1.7.设计模式07-享元模式与组合模式详解
1.7.1.享元模式详解
时长:1h12min
学习目标:
》掌握享元模式与组合模式的应用场景
》 了解享元模式的内部状态和外部状态
》掌握组合模式的透明写法和安全写法
》享元模式和组合模式的优缺点
7.1.享元模式的基本定义
定义:
Flyweight Pattern,享元模式。又称轻量级模式,是对象池的一种实现,类似于线程池,线程池可以避免不停
地创建和销毁多个对象,消耗性能。通过减少对象数量从而改善应用所需的对象结构的方式。
宗旨:
共享细粒度对象,将多个对同一对象的访问集中起来。来达到资源的重复利用。
属于结构型模式。
享元模式在生产中的体现:
房产中介:各类房源共享,全国社保联网。
7.1.1.应用场景
常常应用于系统底层的开发,以便解决系统性能问题。
系统有大量相似对象,需要缓冲池的场景。
7.2.享元模式的code实现
7.2.1.通用写法
1.顶层享元接口定义
一般需要传入外部状态。
package com.wf.flyweight.general; /** * @ClassName IFlyweight * @Description 享元角色,顶层接口 * @Author wf * @Date 2020/5/25 10:07 * @Version 1.0 */ public interface IFlyweight { void operation(String extrinsicState); }
2.定义具体的享元实现
package com.wf.flyweight.general; /** * @ClassName ConcreteFlyweight * @Description 享元实现,因为该类实例需要反复使用,所以,使用工厂类来创建实例 * @Author wf * @Date 2020/5/25 10:10 * @Version 1.0 */ public class ConcreteFlyweight implements IFlyweight { private String intrinsicState; public ConcreteFlyweight(String intrinsicState) { this.intrinsicState = intrinsicState; } @Override public void operation(String extrinsicState) { System.out.println("Object address:"+System.identityHashCode(this)); System.out.println("IntrinsicState:"+this.intrinsicState); System.out.println("ExtrinsicState:"+extrinsicState); } }
3.享元工厂
package com.wf.flyweight.general; import java.util.HashMap; import java.util.Map; /** * @ClassName FlyweightFactory * @Description 享元工厂,因为需要把实例添加到缓存,会创建容器 * @Author wf * @Date 2020/5/25 10:16 * @Version 1.0 */ public class FlyweightFactory { private static Map<String,IFlyweight> pool = new HashMap<String,IFlyweight>(); //因为内部状态具备不变性,因此,作为缓存key public static IFlyweight getFlyweight(String intrinsicState){ if(!pool.containsKey(intrinsicState)){ IFlyweight flyweight = new ConcreteFlyweight(intrinsicState); pool.put(intrinsicState, flyweight); } return pool.get(intrinsicState); } }
注意:
享元对象是多例的
4.系统类图
7.2.2.享元模式的实际使用
在外地工作的同学,每年过春节回家,为了购买火车票,都需要提前一个月进行抢票。如果人工抢票,一般官方放票一两小时,
车票就会抢光了,这时出现很多刷票软件。
刷新软件的实现原理:
会定时检查12306的余票数量【通过sdk或爬虫】,一旦发现有余票,就会把它放到共享池里面,就可以让大家一起来抢票了。
在抢票之前,需要先缓存抢票人的个人信息,出发站,目的地,如果要求匹配,就立马下单,就不需要重复填写信息。就能加快抢票
速度,提高效率。
下面用代码来模拟抢票的过程。
7.2.2.1.创建顶层享元抽象接口
ackage com.wf.flyweight.ticket; /** * @ClassName ITicket * @Description 火车票,享元接口 * @Author wf * @Date 2020/5/25 10:45 * @Version 1.0 */ public interface ITicket { //根据席别区分车票 void showInfo(String bunk); }
7.2.2.2.享元具体实现
package com.wf.flyweight.ticket; import java.util.Random; /** * @ClassName TrainTicket * @Description 火车票,具体享元实现 * @Author wf * @Date 2020/5/25 10:47 * @Version 1.0 */ public class TrainTicket implements ITicket { //列车出发地 private String from; //目的地 private String to; //价格由后台设定,用户无需填写 private int price; public TrainTicket(String from, String to) { this.from = from; this.to = to; } @Override public void showInfo(String bunk) { this.price = new Random().nextInt(500); System.out.println(from+"->"+to+":"+ bunk + ",价格是:"+this.price); } }
7.2.2.3.享元类创建工厂
package com.wf.flyweight.ticket; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * @ClassName TicketFactory * @Description 车票创建工厂 * @Author wf * @Date 2020/5/25 10:55 * @Version 1.0 */ public class TicketFactory { private static Map<String,ITicket> pool = new ConcurrentHashMap<String,ITicket>(); public static ITicket queryTicket(String from, String to){ String key = from +"->"+ to; if(!pool.containsKey(key)){ ITicket ticket = new TrainTicket(from,to); System.out.println("首次查询,创建对象:"+key); pool.put(key,ticket); } return pool.get(key); } }
7.2.2.4.客户调用
package com.wf.flyweight.ticket; /** * @ClassName Test * @Description 客户端调用 * @Author wf * @Date 2020/5/26 16:23 * @Version 1.0 */ public class Test { public static void main(String[] args) { ITicket ticket = TicketFactory.queryTicket("杭州","遵义"); ticket.showInfo("硬座"); } }
测试结果如下:
类图如下所示:
说明:
享元模式,看起来和注册工单例是很像的。然而享元模式的重点,在于系统结构上,而不关心对象创建,是不是单例的。
7.2.3.享元模式应用之数据库连接池
7.2.3.1.顶层享元抽象接口
这个接口已经存在了,可以直接使用jdk提供的Connection接口。
7.2.3.1.享元工厂
package com.wf.flyweight.pool; import java.sql.Connection; import java.sql.DriverManager; import java.util.Vector; /** * @ClassName ConnectionPool * @Description TODO * @Author wf * @Date 2020/5/26 16:54 * @Version 1.0 */ public class ConnectionPool { private Vector<Connection> pool; private int poolSize = 100; private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; public ConnectionPool() { this.pool = new Vector<Connection>(poolSize); try{ Class.forName(driverClassName); for(int i=0;i< poolSize;i++) { Connection conn = DriverManager.getConnection(url, username, password); pool.add(conn); } }catch (Exception e){ e.printStackTrace(); } } public synchronized Connection getConnection(){ if(poolSize > 0){ Connection conn = pool.get(0); pool.remove(conn); return conn; } return null; } public synchronized void release(Connection conn){ pool.add(conn); } }
7.2.3.2.测试类
package com.wf.flyweight.pool; import java.sql.Connection; /** * @ClassName Test * @Description 测试类 * @Author wf * @Date 2020/5/26 17:12 * @Version 1.0 */ public class Test { public static void main(String[] args) { ConnectionPool pool = new ConnectionPool(); Connection conn = pool.getConnection(); System.out.println(conn); } }
测试要想成功,需要本地安装mysql服务器,并且存在test数据库。
还需要添加mysql pom依赖:
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency>
测试结果如下:
7.3.享元模式在源码中的应用
7.3.1.jdk String使用享元模式
package com.wf.flyweight.jdk; /** * @ClassName StringTest * @Description String源码研究测试 * @Author wf * @Date 2020/5/26 17:37 * @Version 1.0 */ public class StringTest { public static void main(String[] args) { //这句声明,创建了几个对象? //s1是在运行阶段声明的,“hello”字符串常量,在编译期就已经完成创建 //常量存储在常量池 String s1= "hello"; String s2= "hello"; System.out.println(s1 == s2);//true,两个变量都从常量池取值 //测试:反例 //两个常量,拼接,在编译阶段就会,得到最终常量,jdk的底层做了优化处理 String s3 = "he" + "llo"; System.out.println(s1 == s3);//true String s4 ="hel" + new String("lo"); System.out.println(s1 == s4);//false String s5 = new String("hello"); System.out.println(s1 == s5);//false System.out.println(s4 == s5);//false String s6 = s5.intern();//从缓存中取值 System.out.println(s1 == s6);//true String s7 = "h"; String s8 = "ello"; String s9 = s7 + s8; System.out.println(s1 == s9);//false } }
7.3.2.Integer使用享元模式
package com.wf.flyweight.jdk; /** * @ClassName IntegerTest * @Description 测试享元模式应用 * @Author wf * @Date 2020/5/26 18:43 * @Version 1.0 */ public class IntegerTest { public static void main(String[] args) { Integer a = Integer.valueOf(127); Integer b = 127; System.out.println(a == b);//true Integer c = Integer.valueOf(128); Integer d = 128; System.out.println(c == d);//false } }
注意:
这里根据经验值,i在【-128,127】之间时,才会缓存值。
特殊处理是在valueOf中进行的。
7.3.3.Long使用享元模式
ValueOf中进行处理。
7.4.享元模式扩展知识
7.4.1.内部状态和外部状态
享元模式,提出两个要求。一是细粒度,一是共享。
细粒度对象,不可避免就会产生很多对象。
内部状态:是享元状态共享出来的东西,它存在享元对象内部,不会跟随环境改变而改变。
外部状态:是指对象得以依赖的一个标记,会随着环境变化而改变。是不可共享的状态。
7.4.2.享元模式的优点和缺点
优点:
减少对象的创建,降低内存中对象的数量,降低系统内存消耗,提高效率。
减少内存之外的其他资源占用。
缺点:
关注内部 ,外部状态,关注线程安全问题
使系统,程序的逻辑复杂化。
7.4.3.享元模式与其他设计模式的关联
7.4.3.1.享元模式与代理模式
静态代理:持有目标对象的引用,是一对一的关系。达到功能增强的目的。
享元模式:也会持有目标对象的引用,是一对多的关系。提高效率,达到资源复用的目的。
7.4.3.2.享元模式与单例模式
单例模式:单例强调对象的创建是否唯一。
享元模式:通常结合工厂模式使用,一般会把享元类设计为单例类。它强调的是系统结构。
1.7.2.组合模式详解
时长:60min
7.2.1.组合模式的定义
定义:
Composite Pattern,也称整体-部分模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)
用相同的接口进行表示。属于结构型模式。
作用:
使客户端对单个对象和组合对象保持一致的方式处理。
组合/聚合区分:
心在一起,称为团队,也可称组合。如:人的组合,身体和头,相互依存,不能分割,这称为组合。
只是人在一起,称团伙,也可称聚合。而像选择授课老师,这种可以灵活选择,称为聚合。U盘和电脑,也是一种聚合关系。
7.2.1.1.生活中组合模式的体现
》公司组织架构
》操作系统的文件管理系统
7.2.1.2.组合模式的适用场景
1.希望客户端可以忽略组合对象与单个对象的差异时
2.对象层次具备整体与部分,呈树形结构(如:树形菜单,操作系统目录,公司组织架构等)
7.2.2.组合模式的代码示例
7.2.2.1.通用写法
组合模式的通常实现,又可分为两种写法:透明写法,和安全写法。
1.透明写法
A.抽象组件
也是一个根节点,代码如下:
package com.wf.composite.transparent; /** * @ClassName Component * @Description 组合对象,顶层抽象组件,根节点 * @Author wf * @Date 2020/5/27 10:00 * @Version 1.0 */ public abstract class Component { protected String name; public Component(String name) { this.name = name; } public abstract String operation(); public boolean addChild(Component component){ throw new UnsupportedOperationException("addChild not supported!"); } public boolean removeChild(Component component){ throw new UnsupportedOperationException("removeChild not supported!"); } public Component getChild(int index){ throw new UnsupportedOperationException("getChild not supported!"); } }
B.树枝节点,抽象组件实现
package com.wf.composite.transparent; import java.util.ArrayList; import java.util.List; /** * @ClassName Composite * @Description 抽象组合,透明实现,实现的父类的空方法,树节点 * @Author wf * @Date 2020/5/27 10:05 * @Version 1.0 */ public class Composite extends Component{ List<Component> mComponents; public Composite(String name) { super(name); mComponents = new ArrayList<Component>(); } @Override public String operation() { StringBuilder builder = new StringBuilder(this.name); for(Component component: this.mComponents){ builder.append(" "); builder.append(component.operation()); } return builder.toString(); } @Override public boolean addChild(Component component) { return this.mComponents.add(component); } @Override public boolean removeChild(Component component) { return this.mComponents.remove(component); } @Override public Component getChild(int index) { return this.mComponents.get(index); } }
C.叶子节点
package com.wf.composite.transparent; /** * @ClassName Leaf * @Description 组合对象,叶子节点 * @Author wf * @Date 2020/5/27 10:12 * @Version 1.0 */ public class Leaf extends Component { public Leaf(String name) { super(name); } @Override public String operation() { return this.name; } }
D,测试代码
package com.wf.composite.transparent; public class Test { public static void main(String[] args) { // 来一个根节点 Component root = new Composite("root"); // 来一个树枝节点 Component branchA = new Composite("---branchA"); Component branchB = new Composite("------branchB"); // 来一个叶子节点 Component leafA = new Leaf("------leafA"); Component leafB = new Leaf("---------leafB"); Component leafC = new Leaf("---leafC"); root.addChild(branchA); root.addChild(leafC); branchA.addChild(leafA); branchA.addChild(branchB); branchB.addChild(leafB); String result = root.operation(); System.out.println(result); } }
测试结果如下:
E.系统类图
之所以,称为透明写法时,Component抽象组件中定义了3个空方法。
2.安全写法
由于透明写法中,抽象组件中定义几个空方法,作为公共组件,Leaf实现类,不需要使用到这几个空方法。
所以,父类中提供的几个功能,并不是公共的。并不是子类中都能用到,所以,现在,去掉这几个方法。
Composite实现中,如果需要自己进行扩展。
A.顶层抽象组件
package com.wf.composite.safe; /** * @ClassName Component * @Description 组合对象,顶层抽象组件,根节点 * @Author wf * @Date 2020/5/27 10:00 * @Version 1.0 */ public abstract class Component { protected String name; public Component(String name) { this.name = name; } public abstract String operation(); }
B.子节点实现
package com.wf.composite.safe; import java.util.ArrayList; import java.util.List; /** * @ClassName Composite * @Description 抽象组合,透明实现,实现的父类的空方法,树节点 * @Author wf * @Date 2020/5/27 10:05 * @Version 1.0 */ public class Composite extends Component { List<Component> mComponents; public Composite(String name) { super(name); mComponents = new ArrayList<Component>(); } @Override public String operation() { StringBuilder builder = new StringBuilder(this.name); for(Component component: this.mComponents){ builder.append(" "); builder.append(component.operation()); } return builder.toString(); } public boolean addChild(Component component) { return this.mComponents.add(component); } public boolean removeChild(Component component) { return this.mComponents.remove(component); } public Component getChild(int index) { return this.mComponents.get(index); } }
C.叶子节点实现
package com.wf.composite.safe; /** * @ClassName Leaf * @Description 组合对象,叶子节点 * @Author wf * @Date 2020/5/27 10:12 * @Version 1.0 */ public class Leaf extends Component { public Leaf(String name) { super(name); } @Override public String operation() { return this.name; } }
D.测试代码
package com.wf.composite.safe; public class Test { public static void main(String[] args) { //由于Composite对抽象组件Component的功能,进行扩展 //就不能使用多态的方式,调用子类的方法 // 来一个根节点 Composite root = new Composite("root"); // 来一个树枝节点 Composite branchA = new Composite("---branchA"); Composite branchB = new Composite("------branchB"); // 来一个叶子节点 Component leafA = new Leaf("------leafA"); Component leafB = new Leaf("---------leafB"); Component leafC = new Leaf("---leafC"); root.addChild(branchA); root.addChild(leafC); branchA.addChild(leafA); branchA.addChild(branchB); branchB.addChild(leafB); String result = root.operation(); System.out.println(result); } }
E.类图查看
3.组合模式的具体实现示例
【1】基于课程需求,透明实现
A.系统需求分析
假设希望输出课程目录信息,如下的结构。以此需求,利用组合模式设计系统,并开发。
初步分析:
上面的结构,有3层目录,可以设计3层嵌套结构。【但是,类太多,不推荐】
可以巧妙用使用两层结构来处理3层目录。第一级目录,设计成课程包类【作为子节点】
具体的课程作为叶子节点。
如:java设计模式,作为java架构师课程的叶子节点。
而java入门课程,也得做为叶子节点。
关于前后两级目录,使用参数进行优先级区分,如果优先级为1,表示第一层根目录。优先级为2,
表示2级目录。
B.测试代码
这里以TDD的开发方式,先编写测试代码,再编写业务逻辑代码。
具体的代码如下:
package com.wf.composite.demo.transparent; public class Test { public static void main(String[] args) { System.out.println("============透明组合模式==========="); CourseComponent javaBase = new Course("Java入门课程",8280); CourseComponent ai = new Course("人工智能",5000); CourseComponent packageCourse = new CoursePackage("Java架构师课程",2); CourseComponent design = new Course("Java设计模式",1500); CourseComponent source = new Course("源码分析",2000); CourseComponent softSkill = new Course("软技能",3000); packageCourse.addChild(design); packageCourse.addChild(source); packageCourse.addChild(softSkill); CourseComponent catalog = new CoursePackage("课程主目录",1); catalog.addChild(javaBase); catalog.addChild(ai); catalog.addChild(packageCourse); catalog.print(); } }
先编写测试代码,以编译报错,驱动编写业务代码。
C.定义课程的抽象组件
package com.wf.composite.demo.transparent; /** * @ClassName CourseComponent * @Description 课程抽象组件 * @Author wf * @Date 2020/5/27 15:22 * @Version 1.0 */ public abstract class CourseComponent { }
为了解决报错,暂时得到一个空类。后面再进行代码填充。
D.创建课程类【叶子节点】
package com.wf.composite.demo.transparent; /** * @ClassName Course * @Description 具体组件实现叶子节点,课程类 * @Author wf * @Date 2020/5/27 15:25 * @Version 1.0 */ public class Course extends CourseComponent { private String name; private int price; public Course(String name, int price) { this.name = name; this.price = price; } }
根据测试提示,需要新增两个成员,课程名称name,和购买价格price.
其它代码,需要进一步填充。
E.创建组件实现的叶子节点,课程归属类
初步实现,会生成构造器,并手动添加成员变量。
再来看,测试类中报错情况:
显然,还有两个方法,未实现引起报错,解决报错,实现方法。
根据提示,有两种实现方式,一种是定义抽象方法,一种是定义普通方法。
结合组合模式透明写法,叶子节点不需要此方法,只能定义为普通方法。并且抽象类中方法不作实现。
这个方法是子节点中必须要实现的方法,所以在子节点类中手动实现。
方法的功能是:
添加1个或多个叶子节点,所以,是一对多的数据关系,定义一个List类型成员来存储多个节点。
再来解决测试类中最后一个报错,print方法未定义的问题。由于这个方法是公用的,提取到父类中,作为抽象方法定义。
抽象组件中定义抽象方法。抽象方法必须在子类中重写,否则子类引起报错。如下:
package com.wf.composite.demo.transparent; /** * @ClassName Course * @Description 具体组件叶子节点,课程类 * @Author wf * @Date 2020/5/27 15:25 * @Version 1.0 */ public class Course extends CourseComponent { /** 课程名称*/ private String name; private double price; public Course(String name, double price) { this.name = name; this.price = price; } @Override public void print() { System.out.println(name + " (¥" + price + "元)"); } }
子节点类,也需要重写抽象方法:
package com.wf.composite.demo.transparent; import java.util.ArrayList; import java.util.List; /** * @ClassName CoursePackage * @Description 组合对象抽象组合,子节点,课程归属类 * @Author wf * @Date 2020/5/27 15:32 * @Version 1.0 */ public class CoursePackage extends CourseComponent { /** 课程归属名称*/ private String name; /** 显示结构优先级*/ private Integer level; private List<CourseComponent> items = new ArrayList<CourseComponent>(); public CoursePackage(String name, Integer level) { this.name = name; this.level = level; } @Override public void addChild(CourseComponent catalogComponent) { items.add(catalogComponent); } @Override public void print() { //需要根据需求设置指定的输出格式 System.out.println(this.name); for(CourseComponent catalogComponent : items){ //控制显示格式 if(this.level != null){ for(int i = 0; i < this.level; i ++){ //打印空格控制格式 System.out.print(" "); } for(int i = 0; i < this.level; i ++){ //每一行开始打印一个+号 if(i == 0){ System.out.print("+"); } System.out.print("-"); } } //打印标题 catalogComponent.print(); } } }
F.抽象组件的完整代码
package com.wf.composite.demo.transparent; /** * @ClassName CourseComponent * @Description 课程抽象组件 * @Author wf * @Date 2020/5/27 15:22 * @Version 1.0 */ public abstract class CourseComponent { public void addChild(CourseComponent catalogComponent) { throw new UnsupportedOperationException("不支持添加操作"); } public abstract void print(); }
G.运行测试
整个系统开发完成,进行最后的测试验证。输出结果如下:
H.系统类图
【2】基于系统文件目录需求,安全实现
A.需求分析
展示结果分析:
上面系统文件目录,存在4级展示目录。还是可以基于展示层次优先级作区分。
B.编写测试代码
具体的测试代码如下:
package com.wf.composite.demo.safe; public class Test { public static void main(String[] args) { System.out.println("============安全组合模式==========="); File qq = new File("QQ.exe"); File wx = new File("微信.exe"); Folder office = new Folder("办公软件",2); File word = new File("Word.exe"); File ppt = new File("PowerPoint.exe"); File excel = new File("Excel.exe"); office.add(word); office.add(ppt); office.add(excel); Folder wps = new Folder("金山软件",3); wps.add(new File("WPS.exe")); office.add(wps); Folder root = new Folder("根目录",1); root.add(qq); root.add(wx); root.add(office); System.out.println("----------show()方法效果-----------"); root.show(); System.out.println("----------list()方法效果-----------"); root.list(); } }
注意:由于这里是基于组合模式的安全写法实现,不能使用多态,没有出现顶层抽象组件依赖。
C.顶层抽象组件定义
基于TDD开发,根据测试代码中报错提示,驱动开发业务代码。
package com.wf.composite.demo.safe; /** * 顶层抽象组件,目录类 */ public abstract class Directory { protected String name; public Directory(String name) { this.name = name; } public abstract void show(); }
D.定义叶子节点File文件类
package com.wf.composite.demo.safe; /** * @ClassName File * @Description 叶子节点,文件类 * @Author wf * @Date 2020/5/27 16:36 * @Version 1.0 */ public class File extends Directory { //private String name; //由于是公共成员,提取到父类中,声明为protected public File(String name) { super(name); } @Override public void show() { System.out.println(this.name); } }
需要手动实现抽象组件,并重写方法。只需要打印名称即可。
E.定义子节点文件夹Folder类
package com.wf.composite.demo.safe; import java.util.ArrayList; import java.util.List; /** * @ClassName Folder * @Description 组合对象抽象组件,子节点 * @Author wf * @Date 2020/5/27 16:41 * @Version 1.0 */ public class Folder extends Directory { private Integer level; private List<Directory> dirs = new ArrayList<Directory>(); public Folder(String name, Integer level) { super(name); this.name = name; this.level = level; } public void add(Directory dir) { dirs.add(dir); } @Override public void show() { //抽象方法,必须重写,设置输出格式 System.out.println(this.name); for (Directory dir : this.dirs) { //控制显示格式 if(this.level != null){ for(int i = 0; i < this.level; i ++){ //打印空格控制格式 System.out.print(" "); } for(int i = 0; i < this.level; i ++){ //每一行开始打印一个+号 if(i == 0){ System.out.print("+"); } System.out.print("-"); } } //打印名称 dir.show(); } } /** * 扩展方法,不能使用多态调用 */ public void list() { for (Directory dir : this.dirs) { System.out.println(dir.name); } } }
F.运行测试
G.系统类图查看
7.2.3.组合模式在源码中的应用
7.2.3.1.jdk中hashMap应用组合模式
public void putAll(Map<? extends K, ? extends V> m) { putMapEntries(m, true); }
说明:
putAll方法,传参抽象组件Map类型。就是组合模式的应用。
7.2.3.2.jdk中ArrayList中addAll方法
public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
说明:
addAll,传参Collection,组合模式应用。
7.2.4.组合模式的问题总结
1.透明写法与安全写法的区别?
透明写法:
顶层抽象组件会定义所有的方法,包括叶子节点不需要的方法。只是作空方法声明。
对于叶子节点,很容易错误调用,所以,违背最少知道原则。
安全写法:
顶层抽象组件,只定义公共方法,调用时就不能使用多态的方式,只能子类自己new自己。
避免叶子节点类实例,错误调用。
组合模式一定是树形结构吗?
答:不一定。只是强调整体与部分的关系。它是针对整体与部分,寻找共同点,进行公共逻辑抽取的思维方式。
7.2.4.1.组合模式的优缺点
优点:
1.清楚地定义层次分明的复杂对象,表示对象的全部或部分层次
2.让客户端忽略层次的差异,方便对整个层次结构进行控制
3.简化客户端代码
4.符合开闭原则
缺点:
1.限制类型时会较为复杂
2.使设计变得更加复杂
附录:
tom老师的刷票软件:https://github.com/gupaoedu-tom/3party-tools/tree/master/py12306