设计模式之美笔记11 ゞ 浴缸里的玫瑰 2022-11-28 13:41 155阅读 0赞 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 ### 文章目录 ### * * 门面模式 * * 门面模式的原理和实现 * 门面模式的应用场景举例 * * 1. 解决易用性问题 * 2. 解决性能问题 * 3. 解决分布式事务问题 * 享元模式 * * 原理 * 享元模式在文本编辑器中的应用 * 享元模式vs单例、缓存、对象池 * * 和单例的区别 * 和缓存的去呗 * 和对象池的区别 * 享元模式在java Integer的应用 * 享元模式在java String的应用 * 组合模式 * * 组合模式的原理和实现 * 组合模式的应用场景举例 ## 门面模式 ## 原理和实现都很简单,应用场景也很明确,主要是接口设计方面使用。 平时开发涉及到接口时,可能会遇到接口粒度的问题。为保证接口的可复用性(或者叫通用性),需要将接口尽量设计的细粒度些,职责单一点。但如果接口的粒度过小,在接口的使用者开发一个业务功能时,会导致需要调用n多细粒度的接口才能完成,调用者就会抱怨接口不好用。 相反,如果粒度过大,一个接口返回n多数据,要做n多事情,会导致接口不够通用,可复用性不好。那针对不同的调用者的业务需求,需要开发不同的接口来满足,导致接口的无限膨胀。 如何解决接口的可复用性和易用性之间的矛盾呢?采用门面模式 ### 门面模式的原理和实现 ### 门面模式,也叫外观模式,facade design pattern,定义:provide a unified interface to a set of interfaces in a subsytem.Facade Pattern defines a higher-level interface that makes the subsystem easier to use. 也即是:门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。子系统有多种理解方式,既可以是一个完整的系统,也可以是更细粒度的类或者模块。 举例说明,假设有个系统A,提供了a、b、c、d四个接口,系统B完成某个业务功能时,需要调用A系统的a、b、d接口,利用门面模式,提供一个包裹a、b、d接口调用的门面接口x给系统B直接使用。 为啥不让系统B直接调用a、b、d接口呢?假设系统A是个后端服务器,系统B是app客户端,客户端通过后端服务器提供的接口来获取数据。app和服务器之间通过移动网络通信的,网络通信耗时较长,为提高app的响应速度,要尽量减少app和服务器的网络通信次数。 假设完成某个业务功能(如显示某个页面信息)需要“依次”调用a、b、d接口,因自身业务特点,不支持并发调用这三个接口。 如果发现app客户端的响应速度较慢,排查后发现,是过多的接口调用过多的网络通信,针对该情况,就可以利用门面模式,让后端服务器提供一个包裹a、b、d接口的接口x,app客户端调用一次接口x,来获取所有想要的数据,将网络通信的次数从3次减少到1次,提高app的响应速度。 这只是门面模式的一个意图,就是解决性能问题。实际上,不同应用场景,使用门面模式的意图也不同。 ### 门面模式的应用场景举例 ### #### 1. 解决易用性问题 #### 门面模式可用来封装系统的底层实现,隐藏系统的复杂性,提供一组简单易用、更高层的接口。如linux系统调用函数就可以看做一种门面。是linux系统暴露给开发者的一组“特殊”的编程接口,封装底层更基础的linux内核调用。 #### 2. 解决性能问题 #### 刚才的例子已经说明,通过将多个接口调用替换为一个门面接口调用,减少网络通信成本,提高app客户端的响应速度。从代码实现的角度,如何组织门面接口和非门面接口呢? 如果门面接口不多,可以将其和非门面接口放到一起,当做普通接口即可。如果门面接口很多,可以在已有的接口之上,重新抽象出一层,专门放置门面接口,从类、包的命名上跟原来的接口层做区分。如果门面太多,并且很多是跨多个子系统的,可放到一个新的子系统中。 #### 3. 解决分布式事务问题 #### 举例说明,在一个金融系统中,有两个业务领域模型,用户和钱包,这两个domain都对外暴露一系列接口,如用户的增删改查接口、钱包的增删改查接口,假设有这样一个业务场景:在用户注册时,不仅会创建用户(在数据库User表),还会给用户创建一个钱包(在数据库Wallet表)。 对这样一个简单的业务需求,通过依次调用用户的创建接口和钱包的创建接口来完成。但是,用户注册需要主持事务,也就是说,要么都成功,要么都失败。要支持两个接口调用在一个事务中执行,较难实现。虽然可通过引入分布式事务框架或者事后补偿的机制解决,但代码实现较为复杂。最简单的就是,利用数据库事务或者spring框架提供的事务,在一个事务中,执行创建用户和创建钱包两个SQL操作,要求两个SQL在一个接口中完成,因此,需要借鉴门面模式,再设计一个包裹两个操作的接口,让新接口在一个事务中执行两个SQL操作。 ## 享元模式 ## ### 原理 ### flyweight design pattern,享元,也即是被共享的单元。意图是复用对象,节省内存,前提是享元对象是不可变对象。 具体说,当一个系统中存在大量重复对象时,如果这些重复的对象是不可变对象,可利用享元模式将对象设计为享元,在内存中只保留一份,供多处代码引用。可以减少内存中对象的数量,节省内存。实际上,不仅相同对象可以设计为享元,对于相似对象,也可将对象相同的部分(字段)提取出来,设计为享元。 “不可变对象”指的是,一旦通过构造函数初始化完成后,它的状态(对象的成员变量或者属性)就不会再被修改。不可变对象不能暴露任何set等修改内部状态的方法。之所以要求享元是不可变对象,因为会被多处代码共享使用。 举例说明。假设在开发一个棋牌游戏(如象棋),一个游戏厅中有成千上万个“房间”,每个房间对应一个棋局。棋局要保存每个棋子的数据,如棋子类型(将、相、士、炮等)、棋子颜色(红方、黑方)、棋子在棋局中的位置。利用这些数据,可以显示一个完整的棋盘给玩家。具体代码如下,其中ChessPiece表示棋子,ChessBoard表示一个棋局,里面保存象棋中30个棋子的信息。 public class ChessPiece { private int id; private String text; private Color color; private int positionX; private int positionY; public ChessPiece(int id, String text, Color color, int positionX, int positionY) { this.id = id; this.text = text; this.color = color; this.positionX = positionX; this.positionY = positionY; } public static enum Color{ RED, BLACK } //...省略其他属性和getter/setter方法... } public class ChessBoard { private Map<Integer, ChessPiece> ChesePiece = new HashMap<>(); public ChessBoard(){ init(); } private void init(){ ChesePiece.put(1,new ChessPiece(1,"车", ChessPiece.Color.BLACK,0,0)); ChesePiece.put(2,new ChessPiece(2,"车", ChessPiece.Color.BLACK,0,1)); //...省略摆放其他棋子的代码... } public void move(int chessPieceId,int toPositionX,int toPositionY){ //...省略... } } 为了记录每个房间当前的棋局情况,需要给每个房间都创建一个ChessBoard棋局对象。因为游戏大厅有成千上万个房间(实际上,百万人同时在线的游戏大厅有很多),那保存这么多棋局对象就会消耗大量的内存。如何节省内存呢? 利用享元模式,像刚才的实现方式,在内存中有大量的相似对象。他们的id、text、color都相同,只有positionX、positionY不同。可以将棋子的id、text、color拆分出来,设计为独立的类,并作为享元供多个棋盘复用。棋盘只需要记录每个棋子的位置信息即可。 public class ChessPieceUnit { private int id; private String text; private Color color; public ChessPieceUnit(int id, String text, Color color) { this.id = id; this.text = text; this.color = color; } public static enum Color{ RED, BLACK } //...省略其他属性和getter/setter方法... } public class ChessPieceUnitFactory { private static final Map<Integer,ChessPieceUnit> pieces = new HashMap<>(); static { pieces.put(1,new ChessPieceUnit(1,"车", ChessPieceUnit.Color.BLACK)); pieces.put(2,new ChessPieceUnit(2,"马", ChessPieceUnit.Color.BLACK)); //...省略摆放其他棋子的代码... } public static ChessPieceUnit getChessPiece(int chessPieceId){ return pieces.get(chessPieceId); } } public class ChessPiece { private ChessPieceUnit chessPieceUnit; private int positionX; private int positionY; public ChessPiece(ChessPieceUnit chessPieceUnit, int positionX, int positionY) { this.chessPieceUnit = chessPieceUnit; this.positionX = positionX; this.positionY = positionY; } //省略getter、setter方法 } public class ChessBoard { private Map<Integer,ChessPiece> chessPieces = new HashMap<>(); public ChessBoard(){ init(); } private void init(){ chessPieces.put(1,new ChessPiece(ChessPieceUnitFactory.getChessPiece(1),0,0)); chessPieces.put(1,new ChessPiece(ChessPieceUnitFactory.getChessPiece(2),1,0)); //...省略摆放其他棋子的代码... } public void move(int chessPieceId,int toPositionX,int toPositionY){ //...省略... } } 上述代码,利用工厂类缓存ChessPieceUnit信息(也就是id、text、color),通过工厂类获取的ChessPieceUnit就是享元。所有的ChessBoard对象共享这30个ChessPieceUnit对象(因为象棋中只有30个棋子)。使用享元之前,记录1万个棋局,创建30万个棋子的ChessPieceUnit对象,而利用享元模式,只需要创建30个享元对象即可。 享元模式的原理讲完了,总结下代码结构。实际上,代码结构非常简单,主要是通过工厂模式,在工厂类中,通过一个map来缓存已经创建过的享元对象,达到复用的目的。 ### 享元模式在文本编辑器中的应用 ### 如何利用享元模式优化文本编辑器的内存占用? 可以把文本编辑器想象为office的Word,不过,假设只实现文字编辑功能,不包含图片、表格等复杂的编辑功能。简化后的文本编辑器,要在内存中表示一个文本文件,只需记录文字和格式两部分信息。格式又包含文字的字体、大小、颜色等信息。 尽管实际编写文档分为标题、正文等文本类型来设置文字的格式,但从理论上说,可给文本文件中的每个文字都设置不同的格式,为实现如此灵活的格式设置,并且代码实现又不过于复杂,把每个文字都看做一个独立的对象看待,并在其中包含它的格式信息。具体代码: public class Character { //文字 private char c; private Font font; private int size; private int colorRGB; public Character(char c, Font font, int size, int colorRGB) { this.c = c; this.font = font; this.size = size; this.colorRGB = colorRGB; } } public class Editor { private List<Character> chars = new ArrayList<>(); public void appendCharacter(char c, Font font,int size,int colorRGB){ Character character = new Character(c,font,size,colorRGB); chars.add(character); } } 在文字编辑器中,每敲一个字,都会调用Editor类的appendCharacter()方法,创建一个新的Character对象,保存到chars数组中,如果有几十万个文字,要在内存中存储这么多Character对象,如何节省内存? 实际上,在一个文本文件中,用到的字体格式不会太多,毕竟不可能把每个文字都设置为不同的格式。对应字体格式,可设计为享元,让不同的文字共享使用。按这个设计思路,对代码重构: public class CharacterStyle { private Font font; private int size; private int colorRGB; public CharacterStyle(Font font, int size, int colorRGB) { this.font = font; this.size = size; this.colorRGB = colorRGB; } @Override public boolean equals(Object obj) { CharacterStyle otherStyle = (CharacterStyle) obj; return font.equals(otherStyle.font) && size==otherStyle.size && colorRGB==otherStyle.colorRGB; } } public class CharacterStyleFactory { private static final List<CharacterStyle> styles = new ArrayList<>(); public static CharacterStyle getStyle(Font font,int size,int colorRGB){ CharacterStyle newStyle = new CharacterStyle(font,size,colorRGB); for (CharacterStyle style:styles){ if (style.equals(newStyle)){ return style; } } styles.add(newStyle); return newStyle; } } public class Character { private char c; private CharacterStyle style; public Character(char c, CharacterStyle style) { this.c = c; this.style = style; } } public class Editor { private List<Character> chars = new ArrayList<>(); public void appendCharacter(char c, Font font, int size, int colorRGB){ Character character = new Character(c,CharacterStyleFactory.getStyle(font, size, colorRGB)); chars.add(character); } } ### 享元模式vs单例、缓存、对象池 ### #### 和单例的区别 #### 单例模式中,一个类只能创建一个对象,而在享元模式中,一个类可以创建多个对象,每个对象被多处代码引用共享。实际上,享元模式类似于之前的单例的变体:多例。 区别两种设计模式,不能只看代码实现,而是看设计意图,也就是要解决的问题,尽管从代码实现上看,享元模式和多例有很多相似之处,但从设计意图上,完全不同。应用享元模式是为了对象复用,节省内存,而多例模式是为了限制对象的个数。 #### 和缓存的去呗 #### 享元模式的实现中,通过工厂类来“缓存”已经创建好的对象。这里的“缓存”实际上是“存储”的意思,跟平时说的数据库缓存、CPU缓存等是两回事。平时的缓存,是为了提高访问效率,而非复用。 #### 和对象池的区别 #### 对象池、连接池、线程池也是为了复用,和享元模式的区别在哪里? 简单解释下对象池,像C++这样的编程语言,内存管理由程序员自己负责,为避免频繁的进行对象创建和释放导致内存碎片,可预先申请一片连续的内存空间,也就是对象池。每次创建对象时,从对象池取出一个空闲对象使用,对象使用完成再放回对象池供复用,而非直接释放掉。 池化技术的复用,可理解为重复使用,目的是节省时间(如从数据库连接池中取一个连接,不用重新创建),在任意时刻,每个对象、连接、线程,并不会被多处使用,而是被一个使用者独占,当使用完成,放回池中,由其他使用者复用。享元模式的复用可理解为共享使用,整个生命周期,都被所有使用者共享,目的是节省空间。 ### 享元模式在java Integer的应用 ### 先看代码,思考输出的结果 Integer i1 = 56; Integer i2 = 56; Integer i3 = 129; Integer i4 = 129; System.out.println(i1==i2); System.out.println(i3==i4); 要正确分析,首先搞清楚两个事情: * 如何判定两个java对象是否相等(也就是代码中的“==”操作符的含义) * 什么是自动装箱autoboxing和自动拆箱unboxing 自动装箱,就是自动将基本数据类型转换为包装器类型,自动拆箱就是自动将包装器类型转换为基本数据类型。 Integer i = 56;//自动装箱 int j = i;//自动拆箱 数值56是基本数据类型int,赋值给包装器类型Integer变量时,触发自动装箱操作,创建一个Integer类型的对象,复制给变量i,底层相当于执行下面这条语句: Integer i = 56; 底层执行:Integer i = Integer.valueOf(56); 反过来 int j=i; 底层执行了: int j = i.intValue(); 那如何判定两个对象是否相等?实际就是判定两个局部变量存储的地址是否相同,也即是判定两个局部变量是否指向相同的对象。 再看之前的代码。前4行复制语句都会触发自动装箱操作,也即是创建Integer对象并赋值给i1、i2、i3、i4四个变量,根据刚才的理解,i1、i2指向的是不同的Integer对象,通过“”判定时返回false,同理,i3i4也是false。 但实际上,答案并不是两个false,而是一个true,一个false。为什么呢?因为Integer利用了享元模式复用对象,导致上述的结果。自动装箱时,也即是调用valueOf()创建Integer对象,如果创建的Integer对象的值在-128到127之间,会从IntegerCache类中直接返回,否则才调用new方法创建,对应的源码: public static Integer valueOf(int i){ if(i>=IntegerCache.low && i<=IntegerCache.high){ return IntegerCache.cache[i+(-IntegerCache.low)]; } return new Integer(i); } 实际上,这个IntegerCache相当于生成享元对象的工厂类,只是名字没有factory,是Integer的内部类,具体代码实现: private static class IntegerCache{ static final int low = -128; static final int high; static final Integer cache[]; static { //high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if(integerCacheHighPropValue != null){ try{ int i = parseInt(integerCacheHighPropValue); i = Math.max(i,127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i,Integer.MAX_VALUE - (-low) -1); }catch(NumberFormatException nfe){ // if the property cannot be parsed into an int,ignore it } } high = h; cache = new Integer[(high-low)+1]; int j = low; for(int k=0;k<cache.length;k++){ cache[k] = new Integer(j++); } //range[-128,127] must be interned (JLS7 5.1.7) assert IntegerCache.high>=127; } private IntegerCache(){ } } IntegerCache为何只缓存-128到127之间的整型值?这区间的最常用,也即是一个字节的大小。 因此,平时开发时,对于下面三种创建整型对象的方式,优先使用后两种: Integer a = new Integer(123); Integer a = 123; Integer a = Integer.valueOf(123); ### 享元模式在java String的应用 ### 还是先看一段代码: String s1 = "aaa"; String s2 = "aaa"; String s3 = new String("aaa"); System.out.println(s1==s2); System.out.println(s1==s3); 运行结果,一个true一个false,和Integer设计思路类似,String类利用享元模式复用相同的字符串常量,jvm会专门开辟一块存储区来存储字符串常量,这块存储区叫“字符串常量池”。和Integer的不同之处是,Integer要共享的对象,是在类加载的时候,集中一次性创建好,但对字符串来说,无法预知共享哪些字符串常量,只能在某个字符串常量第一次被用到时,存储到常量池,之后再用到时,直接引用常量池中已存在的即可。 ## 组合模式 ## ### 组合模式的原理和实现 ### GoF的《设计模式》中定义:compose objects into tree structure to represent part-whole hierarchies. Composite lets client treat individual objects and compositions of objects uniformly. 将一组对象组织(compose)成树形结构,以表示一种“部分-整体”的层次结构,组合让客户端(代指代码的使用者)可以统一单个对象和组合对象的处理逻辑。 举例说明,假设有这样一个需求:设计一个类来表示文件系统中的目录,能方便的实现下面的功能: * 动态的添加、删除某个目录下的子目录或文件 * 统计指定目录下的文件个数 * 统计指定目录下的文件总大小 给出骨架代码,核心逻辑并未实现,代码实现中,把文件和目录统一用FileSystemNode类表示,通过isFile属性区分。 public class FileSystemNode { private String path; private boolean isFile; private List<FileSystemNode> subNodes = new ArrayList<>(); public FileSystemNode(String path, boolean isFile) { this.path = path; this.isFile = isFile; } public int countNumOfFiles(){ //todo } public long countSizeOfFile(){ //todo } public String getPath(){ return path; } public void addSubNode(FileSystemNode fileOrDir){ subNodes.add(fileOrDir); } public void removeSubNode(FileSystemNode fileOrDir){ int size = subNodes.size(); int i = 0; for (;i<size;i++){ if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())){ break; } } if (i<size){ subNodes.remove(i); } } } 具体补全countNumOfFiles()和countSizeOfFiles()这两个函数,其实就是树上的递归遍历算法。对于文件,直接返回文件的个数(返回1)或大小。对于,目录,遍历目录中的每个子目录或者文件,递归计算他们的个数或大小,然后求和,就是目录下的文件个数和文件大小。 具体实现: public int countNumOfFiles(){ if (isFile){ return 1; } int numOfFiles = 0; for (FileSystemNode fileOrDir:subNodes){ numOfFiles += fileOrDir.countNumOfFiles(); } return numOfFiles; } public long countSizeOfFile(){ if (isFile){ File file = new File(path); if (!file.exists()) return 0; return file.length(); } long sizeOfFiles = 0; for (FileSystemNode fileOrDir:subNodes){ sizeOfFiles += fileOrDir.countSizeOfFile(); } return sizeOfFiles; } 单纯从功能实现的角度,代码没问题,但是如果开发的是大型系统,从扩展性(文件或目录可能会对应不同的操作)、业务建模(文件和目录从业务上是两个概念)、代码的可读性(文件和目录区分对待更符合人们对业务的认知)的角度,最好对文件和目录区分设计,定义为File和Directory两个类。重构后 public abstract class FileSystemNode { protected String path; public FileSystemNode(String path) { this.path = path; } public abstract int countNumOfFiles(); public abstract long countSizeOfFile(); public String getPath(){ return path; } } public class File extends FileSystemNode { public File(String path) { super(path); } @Override public int countNumOfFiles() { return 1; } @Override public long countSizeOfFile() { java.io.File file = new java.io.File(path); if (!file.exists()) return 0; return file.length(); } } public class Directory extends FileSystemNode { private List<FileSystemNode> subNodes = new ArrayList<>(); public Directory(String path) { super(path); } @Override public int countNumOfFiles() { int numOfFiles = 0; for (FileSystemNode fileOrDir:subNodes){ numOfFiles += fileOrDir.countNumOfFiles(); } return numOfFiles; } @Override public long countSizeOfFile() { long sizeOfFiles = 0; for (FileSystemNode fileOrDir:subNodes){ sizeOfFiles += fileOrDir.countSizeOfFile(); } return sizeOfFiles; } public void addSubNode(FileSystemNode fileOrDir){ subNodes.add(fileOrDir); } public void removeSubNode(FileSystemNode fileOrDir){ int size = subNodes.size(); int i = 0; for (;i<size;i++){ if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())){ break; } } if (i<size){ subNodes.remove(i); } } } 再看如何用他们表示一个文件系统中的目录树结构。 public class Demo { public static void main(String[] args) { /* / /wz/ /wz/a.txt /wz/b.txt /wz/movies/ /wz/movies/c.avi /xzg/ /xzg/docs/ /xzg/docs/d.txt */ Directory fileSytemTree = new Directory("/"); Directory node_wz = new Directory("/wz/"); Directory node_xzg = new Directory("/xzg/"); fileSytemTree.addSubNode(node_wz); fileSytemTree.addSubNode(node_xzg); File node_wz_a = new File("/wz/a.txt"); File node_wz_b = new File("/wz/b.txt"); Directory node_wz_movies = new Directory("/wz/movies/"); node_wz.addSubNode(node_wz_a); node_wz.addSubNode(node_wz_b); node_wz.addSubNode(node_wz_movies); File node_wz_movies_c = new File("/wz/movies/c.avi"); node_wz_movies.addSubNode(node_wz_movies_c); Directory node_xzg_docs = new Directory("/xzg/docs/"); node_xzg.addSubNode(node_xzg_docs); File node_xzg_docs_d = new File("/xzg/docs/d.txt"); node_xzg_docs.addSubNode(node_xzg_docs_d); System.out.println("/ files num:"+ fileSytemTree.countNumOfFiles()); System.out.println("/wz/ files num:"+node_wz.countNumOfFiles()); } } 对照着例子,再看组合模式的定义:将一组对象(文件和目录)组织成树形结构,以表示一种“部分-整体”的层次结构(目录与子目录的嵌套结构)。组合模式让客户端可以统一单个对象(文件)和组合对象(目录)的处理逻辑(递归遍历)。 与其说组合模式是设计模式,不如说是对业务场景的一种数据结构和算法的抽象。其中,数据可以表示为树种种数据结构,业务需求可以通过在树上的递归遍历算法实现。 ### 组合模式的应用场景举例 ### 再举个例子,假设在开发一个OA系统(办公自动化系统),公司的组织结构包含部门和员工两种数据类型,其中,部门又包含子部门和员工。在数据库的表结构如下。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dqbDMxODAy_size_16_color_FFFFFF_t_70_pic_center] 希望在内存中构建整个公司的人员架构图(部门、子部门、员工的隶属关系),并且提供接口计算出部门的薪资成本(隶属该部门的所有员工的薪资和)。 部门包含子部门和员工,这是种嵌套结构,可表示为树这种数据结构,计算需求,也可在树上的遍历算法来实现。从这个角度,该应用场景可使用组合模式设计和实现。 其代码结构和上个例子很相似。 public abstract class HumanResource { protected long id; protected double salary; public HumanResource(long id) { this.id = id; } public long getId(){ return id; } public abstract double calculateSalary(); } public class Employee extends HumanResource { public Employee(long id,double salary) { super(id); this.salary = salary; } @Override public double calculateSalary() { return salary; } } public class Department extends HumanResource { private List<HumanResource> subNodes = new ArrayList<>(); public Department(long id) { super(id); } @Override public double calculateSalary() { double totalSalary = 0; for (HumanResource hr: subNodes){ totalSalary += hr.calculateSalary(); } this.salary = totalSalary; return salary; } public void addSunNode(HumanResource hr){ subNodes.add(hr); } } //组织架构的代码 public class Demo { private static final long ORGANIZATION_ROOT_ID = 1001; private DepartmentRepo departmentRepo;//依赖注入 private EmployeeRepo employeeRepo;//依赖注入 public void buildOrganization(){ Department rootDepartment = new Department(ORGANIZATION_ROOT_ID); buildOrganization(rootDepartment); } private void buildOrganization(Department department){ List<Long> subDepartmentIds = departmentRepo.getSubDepartmentIds(department); for (Long subDepartmentId:subDepartmentIds){ Department subDepartment = new Department(subDepartmentId); department.addSunNode(subDepartment); buildOrganization(subDepartment); } List<Long> employeeIds = employeeRepo.getDepartmentEmployeeIds(department.getId()); for (Long employeeId:employeeIds){ double salary = employeeRepo.getEmployeeSalary(employeeId); department.addSunNode(new Employee(employeeId,salary)); } } } 再拿组合模式的定义和这个例子对照:将一组对象(员工和部门)组织成树形结构,以表示一种“部分-整体”的层次结构(部门和子部门的嵌套结构)。组合模式让客户端可统一单个对象(员工)和组合对象(部门)的处理逻辑(递归遍历)。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dqbDMxODAy_size_16_color_FFFFFF_t_70_pic_center]: /images/20221124/403d875540bf492094daadf44de23a14.png
相关 设计模式之美笔记16 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 解释器模式 解释器模式的原理和实现 深藏阁楼爱情的钟/ 2022年12月01日 11:53/ 0 赞/ 136 阅读
相关 设计模式之美笔记15 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 访问者模式 访问者模式的诞生 我就是我/ 2022年12月01日 05:16/ 0 赞/ 143 阅读
相关 设计模式之美笔记14 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 状态模式 背景 什么 水深无声/ 2022年11月30日 15:51/ 0 赞/ 146 阅读
相关 设计模式之美笔记13 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 策略模式 策略模式的原理和实现 忘是亡心i/ 2022年11月30日 12:27/ 0 赞/ 145 阅读
相关 设计模式之美笔记12 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 观察者模式 原理及应用场景剖析 深碍√TFBOYSˉ_/ 2022年11月30日 04:18/ 0 赞/ 174 阅读
相关 设计模式之美笔记11 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 门面模式 门面模式的原理和实现 ゞ 浴缸里的玫瑰/ 2022年11月28日 13:41/ 0 赞/ 156 阅读
相关 设计模式之美笔记10 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 序言 代理模式 桥接模式 柔情只为你懂/ 2022年11月28日 10:36/ 0 赞/ 149 阅读
相关 设计模式之美笔记9 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 工厂模式 1. 简单工厂 待我称王封你为后i/ 2022年11月28日 00:41/ 0 赞/ 148 阅读
相关 设计模式之美笔记8 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 单例模式 1. 为什么要使用单例 柔光的暖阳◎/ 2022年11月26日 07:52/ 0 赞/ 154 阅读
相关 设计模式之美笔记7 > 记录学习王争的设计模式之美 课程 笔记和练习代码,以便回顾复习,共同进步 文章目录 实战1:id生成器的重构 1. 需求背景 女爷i/ 2022年11月25日 13:19/ 0 赞/ 187 阅读
还没有评论,来说两句吧...