设计模式
重点:单例模式、工厂方法模式、建造者模式、适配器模式(类/对象)、代理模式、装饰器模式、迭代器模式、模板方法模式、策略模式、责任链模式、观察者模式
1、设计模式的分类
创建型模式
用于描述“怎么样创建对象”,它的主要特征是“将对象的创建与使用分离”。GOF书中提到了单例、原型、工厂方法、抽象工厂、建造者等5种创建型模式。
结构型模式
用于描述如何将类或对象按某种布局组成更大的机构,GOF书中提到了代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式。
行为模式
用于描述类或对象怎么样相互协作共同完成单个对象无法单独完成的任务,以及怎么样分配职责。GOF书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等11种行为模式。
2、软件设计原则
在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据6条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。
开闭原则
对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
想要达到这样的效果,我们要使用接口和抽象类。
因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
1 package com.itheima.principles.demo1; 2 3 /** 4 * 设计原则:开闭原则 5 * 案例:搜狗输入法皮肤案例 抽象皮肤类 6 */ 7 public abstract class AbstractSkin { 8 9 /** 10 * 显示皮肤抽象方法 11 */ 12 public abstract void display(); 13 }
1 package com.itheima.principles.demo1; 2 3 /** 4 * 设计原则:开闭原则 5 * 案例:搜狗输入法皮肤案例 默认皮肤类 6 */ 7 public class DefaultSkin extends AbstractSkin { 8 @Override 9 public void display() { 10 System.out.println("默认皮肤"); 11 } 12 }
1 package com.itheima.principles.demo1; 2 3 /** 4 * 设计原则:开闭原则 5 * 案例:搜狗输入法皮肤案例 黑马皮肤类 6 */ 7 public class HeimaSkin extends AbstractSkin { 8 @Override 9 public void display() { 10 System.out.println("黑马皮肤"); 11 } 12 }
1 package com.itheima.principles.demo1; 2 3 /** 4 * 设计原则:开闭原则 5 * 案例:搜狗输入法皮肤案例 搜狗输入法类 6 */ 7 public class SougouInput { 8 private AbstractSkin skin; 9 10 public void setSkin(AbstractSkin skin) { 11 this.skin = skin; 12 } 13 14 public void display() { 15 skin.display(); 16 } 17 }
1 package com.itheima.principles.demo1; 2 3 /** 4 * 测试类 5 */ 6 public class Client { 7 public static void main(String[] args) { 8 // 1 创建搜狗输入法对象 9 SougouInput sougouInput = new SougouInput(); 10 // 2 创建默认皮肤对象 11 DefaultSkin defaultSkin = new DefaultSkin(); 12 // 3 设置默认皮肤到输入法 13 sougouInput.setSkin(defaultSkin); 14 // 4 展示皮肤 15 sougouInput.display(); 16 17 // 设置黑马皮肤并展示 18 HeimaSkin heimaSkin = new HeimaSkin(); 19 sougouInput.setSkin(heimaSkin); 20 sougouInput.display(); 21 } 22 }
以上代码搜狗皮肤案例实现了开闭原则,定义了抽象皮肤类,让特定皮肤类(默认皮肤、黑马皮肤)继承抽象皮肤类,去实现特定的皮肤功能,在搜狗输入法使用时,只需要设置具体的皮肤给输入法就可以实现换肤,不用修改原来的代码,并且自己设计一个皮肤时,只需要重新定义一个类即可,方便扩展,在使用时实现了热插拔。
里氏代换原则
里氏代换原则是面向对象设计的基本原则之一。
里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。话句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
1 package com.itheima.principles.demo2.before; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 长方形类 6 */ 7 public class Rectangle { 8 private double length; 9 private double width; 10 11 public double getLength() { 12 return length; 13 } 14 15 public void setLength(double length) { 16 this.length = length; 17 } 18 19 public double getWidth() { 20 return width; 21 } 22 23 public void setWidth(double width) { 24 this.width = width; 25 } 26 }
1 package com.itheima.principles.demo2.before; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 正方形类 6 */ 7 public class Square extends Rectangle { 8 @Override 9 public void setLength(double length) { 10 super.setLength(length); 11 super.setWidth(length); 12 } 13 14 @Override 15 public void setWidth(double width) { 16 super.setWidth(width); 17 super.setLength(width); 18 } 19 }
1 package com.itheima.principles.demo2.before; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 测试类 6 */ 7 public class RectangleDemo { 8 public static void main(String[] args) { 9 // 创建长方形对象 10 Rectangle rectangle = new Rectangle(); 11 // 设置长宽 12 rectangle.setWidth(10); 13 rectangle.setLength(20); 14 // 调用resize方法进行扩宽 15 resize(rectangle); 16 // 打印长和宽 17 printLengthAndWidth(rectangle); 18 19 System.out.println("============="); 20 21 // 创建正方形对象 22 Square square = new Square(); 23 square.setLength(20); 24 // 调用resize方法进行扩宽 25 resize(square); 26 // 打印长和宽 27 printLengthAndWidth(square); 28 } 29 30 // 扩宽方法 31 public static void resize(Rectangle rectangle) { 32 while (rectangle.getWidth() <= rectangle.getLength()) { 33 rectangle.setWidth(rectangle.getWidth() + 1); 34 } 35 } 36 37 // 打印长宽 38 public static void printLengthAndWidth(Rectangle rectangle) { 39 System.out.println(rectangle.getLength()); 40 System.out.println(rectangle.getWidth()); 41 } 42 }
我们运行一下这段代码就会发现,假如我们把一个普通长方形参数传入resize方法,就会看到长方形宽度逐渐增长的效果,当宽度大于长度,代码就会停止,这种行为的结果符合我们的预期;假如我们再把一个正方形作为参数传入resize方法后,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。所有,普通的长方形适合这段代码,正方形不适合。
我们得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违法了里氏代换原则,他们之间的继承关系不成立,正方形不是长方形。
改进:此时我们需要重新设计他们之间的关系。抽象出来一个四边形接口(Quadrilateral),让Rectangle类和Square类实现Quadrilateral接口,以下是改进后的实现。
1 package com.itheima.principles.demo2.after; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 四边形接口 6 */ 7 public interface Quadrilateral { 8 9 /** 10 * 获取宽 11 * @return 12 */ 13 double getWidth(); 14 15 /** 16 * 获取长 17 * @return 18 */ 19 double getLength(); 20 }
1 package com.itheima.principles.demo2.after; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 长方形类 6 */ 7 public class Rectangle implements Quadrilateral { 8 private double length; 9 private double width; 10 11 public void setLength(double length) { 12 this.length = length; 13 } 14 15 public void setWidth(double width) { 16 this.width = width; 17 } 18 19 @Override 20 public double getWidth() { 21 return width; 22 } 23 24 @Override 25 public double getLength() { 26 return length; 27 } 28 }
1 package com.itheima.principles.demo2.after; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 正方形类 6 */ 7 public class Square implements Quadrilateral { 8 9 private double side; 10 11 @Override 12 public double getWidth() { 13 return side; 14 } 15 16 @Override 17 public double getLength() { 18 return side; 19 } 20 21 public double getSide() { 22 return side; 23 } 24 25 public void setSide(double side) { 26 this.side = side; 27 } 28 }
1 package com.itheima.principles.demo2.after; 2 3 /** 4 * 设计原则:里氏代换原则 5 * 案例:正方形不是长方形案例 测试类 6 */ 7 public class RectangleDemo { 8 public static void main(String[] args) { 9 // 创建长方形对象 10 Rectangle rectangle = new Rectangle(); 11 // 设置从宽和长 12 rectangle.setWidth(10); 13 rectangle.setLength(20); 14 // 扩宽 15 resize(rectangle); 16 // 打印长和宽 17 printLengthAndWidth(rectangle); 18 } 19 20 // 扩宽的方法 21 public static void resize(Rectangle rectangle) { 22 while (rectangle.getWidth() <= rectangle.getLength()) { 23 rectangle.setWidth(rectangle.getWidth() + 1); 24 } 25 } 26 27 // 打印长和宽 28 public static void printLengthAndWidth(Quadrilateral quadrilateral) { 29 System.out.println(quadrilateral.getLength()); 30 System.out.println(quadrilateral.getWidth()); 31 } 32 }
改进后的方案不是让正方形继承长方形,而是定义一个四边形接口,让正方形和长方形分别去实现。
依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
1 package com.itheima.principles.demo3.before; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 Intel cpu 类 6 */ 7 public class IntelCpu { 8 public void run() { 9 System.out.println("使用Intel处理器"); 10 } 11 }
1 package com.itheima.principles.demo3.before; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 希捷银盘类 6 */ 7 public class XiJieHardDisk { 8 // 存储数据的方法 9 public void save(String data) { 10 System.out.println("使用希捷硬盘存储数据为:" + data); 11 } 12 13 // 获取数据的方法 14 public String get() { 15 System.out.println("使用希捷银盘读取数据"); 16 return "数据"; 17 } 18 19 }
1 package com.itheima.principles.demo3.before; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 金士顿内存条类 6 */ 7 public class KingstonMemory { 8 public void save() { 9 System.out.println("使用金士顿内存条"); 10 } 11 }
1 package com.itheima.principles.demo3.before; 2 3 public class Computer { 4 private XiJieHardDisk hardDisk; 5 private IntelCpu cpu; 6 private KingstonMemory memory; 7 8 public XiJieHardDisk getHardDisk() { 9 return hardDisk; 10 } 11 12 public void setHardDisk(XiJieHardDisk hardDisk) { 13 this.hardDisk = hardDisk; 14 } 15 16 public IntelCpu getCpu() { 17 return cpu; 18 } 19 20 public void setCpu(IntelCpu cpu) { 21 this.cpu = cpu; 22 } 23 24 public KingstonMemory getMemory() { 25 return memory; 26 } 27 28 public void setMemory(KingstonMemory memory) { 29 this.memory = memory; 30 } 31 32 // 运行计算机 33 public void run() { 34 35 System.out.println("运行计算机"); 36 String data = hardDisk.get(); 37 System.out.println("从硬盘上获取的数据是:" + data); 38 39 cpu.run(); 40 41 memory.save(); 42 } 43 }
1 package com.itheima.principles.demo3.before; 2 3 public class ComputerDemo { 4 public static void main(String[] args) { 5 // 创建计算机对象 6 Computer computer = new Computer(); 7 8 // 创建硬盘 9 XiJieHardDisk hardDisk = new XiJieHardDisk(); 10 computer.setHardDisk(hardDisk); 11 12 IntelCpu cpu = new IntelCpu(); 13 computer.setCpu(cpu); 14 15 KingstonMemory memory = new KingstonMemory(); 16 computer.setMemory(memory); 17 18 computer.run(); 19 } 20 }
组装电脑案例:现在要组装一台电脑,需要配件cpu,硬盘,内存条。只有这些配置都有了,计算机才能正常运行。选择cpu有很多选择,如Intel,AMD等,硬盘可以选择希捷,西数等,内存条可以选择金士顿、海盗船等。
上面的代码可以看到已经组装了一台电脑,但是似乎组装的电脑的cpu只能是Intel的,内存条只能是金士顿的,硬盘只能是希捷的,这对用户肯定是不友好的,用户有了机箱肯定是想按照自己的喜好,选择自己喜欢的配件。
1 package com.itheima.principles.demo3.after; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 cpu接口 6 */ 7 public interface Cpu { 8 void run(); 9 }
1 package com.itheima.principles.demo3.after; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 Intel cpu 类 6 */ 7 public class IntelCpu implements Cpu { 8 @Override 9 public void run() { 10 System.out.println("使用Intel处理器"); 11 } 12 }
1 package com.itheima.principles.demo3.after; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 Intel cpu 类 6 */ 7 public class AMDCpu implements Cpu { 8 @Override 9 public void run() { 10 System.out.println("使用AMD处理器"); 11 } 12 }
1 package com.itheima.principles.demo3.after; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 计算机类 6 */ 7 public class Computer { 8 private HardDisk hardDisk; 9 private Cpu cpu; 10 private Memory memory; 11 12 public HardDisk getHardDisk() { 13 return hardDisk; 14 } 15 16 public void setHardDisk(HardDisk hardDisk) { 17 this.hardDisk = hardDisk; 18 } 19 20 public Cpu getCpu() { 21 return cpu; 22 } 23 24 public void setCpu(Cpu cpu) { 25 this.cpu = cpu; 26 } 27 28 public Memory getMemory() { 29 return memory; 30 } 31 32 public void setMemory(Memory memory) { 33 this.memory = memory; 34 } 35 36 // 运行计算机 37 public void run() { 38 System.out.println("运行计算机"); 39 String data = hardDisk.get(); 40 System.out.println("从硬盘上获取的数据是:" + data); 41 42 cpu.run(); 43 44 memory.save(); 45 } 46 }
1 package com.itheima.principles.demo3.after; 2 3 /** 4 * 设计原则:依赖倒转原则【开闭原则的具体实现】 5 * 案例:组装电脑案例 测试类 6 */ 7 public class ComputerDemo { 8 public static void main(String[] args) { 9 10 // 创建计算机的组件对象 11 HardDisk hardDisk = new XiJieHardDisk(); 12 // Cpu cpu = new IntelCpu(); 13 Cpu cpu = new AMDCpu(); 14 Memory memory = new KingstonMemory(); 15 16 // 创建计算机对象 17 Computer computer = new Computer(); 18 // 组装计算机对象 19 computer.setHardDisk(hardDisk); 20 computer.setCpu(cpu); 21 computer.setMemory(memory); 22 23 // 运行计算机对象 24 computer.run(); 25 } 26 }
根据依赖倒转原则进行改进(以上改进代码只展示部分代码):
代码我们只需要修改Computer类,让Computer类依赖抽象(各个配件的接口),而不是依赖于各个组件具体的实现类。
接口隔离原则
客户端不应该被迫依赖于它不使用的方法;一个类对另外一个类的依赖应该建立在最小的接口上。
安全门案例:
我们需要创建一个黑马品牌的安全门,该安全门具有防火、防水、防盗的功能。可以将防火、防水、防盗功能提取成一个接口。
上面的设计我们发现了它存在问题,黑马品牌的安全门具有防火、防水、防盗的功能。现在如果我们还需要创建一个传智品牌的安全门,而该安全门只具有防火、防盗功能呢?很显然SafetyDoor接口就违背了接口隔离原则。
1 package com.itheima.principles.demo4.before; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 安全门接口 6 */ 7 public interface SafetyDoor { 8 // 防盗 9 void antiTheft(); 10 11 // 防火 12 void fireProof(); 13 14 // 防水 15 void waterProof(); 16 }
1 package com.itheima.principles.demo4.before; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 黑马品牌的安全门 6 */ 7 public class HeimaSafetyDoor implements SafetyDoor { 8 @Override 9 public void antiTheft() { 10 System.out.println("防盗"); 11 } 12 13 @Override 14 public void fireProof() { 15 System.out.println("防火"); 16 } 17 18 @Override 19 public void waterProof() { 20 System.out.println("防水"); 21 } 22 }
1 package com.itheima.principles.demo4.before; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 测试类 6 */ 7 public class Client { 8 public static void main(String[] args) { 9 HeimaSafetyDoor door = new HeimaSafetyDoor(); 10 door.antiTheft(); 11 door.fireProof(); 12 door.waterProof(); 13 } 14 }
改进:将不同功能的接口进行拆分
1 package com.itheima.principles.demo4.after; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 防盗接口 6 */ 7 public interface AntiTheft { 8 9 // 防盗 10 void antiTheft(); 11 }
1 package com.itheima.principles.demo4.after; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 防火接口 6 */ 7 public interface FireProof { 8 9 // 防火 10 void fireProof(); 11 }
1 package com.itheima.principles.demo4.after; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 防水接口 6 */ 7 public interface WaterProof { 8 9 // 防水 10 void waterProof(); 11 }
1 package com.itheima.principles.demo4.after; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 黑马品牌安全门 6 */ 7 public class HeimaSafetyDoor implements AntiTheft, FireProof, WaterProof { 8 @Override 9 public void antiTheft() { 10 System.out.println("防盗"); 11 } 12 13 @Override 14 public void fireProof() { 15 System.out.println("防火"); 16 } 17 18 @Override 19 public void waterProof() { 20 System.out.println("防水"); 21 } 22 }
1 package com.itheima.principles.demo4.after; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 传智安全门【防盗、防火】 6 */ 7 public class ItcastSafetyDoor implements AntiTheft, FireProof { 8 @Override 9 public void antiTheft() { 10 System.out.println("防盗"); 11 } 12 13 @Override 14 public void fireProof() { 15 System.out.println("防火"); 16 } 17 }
1 package com.itheima.principles.demo4.after; 2 3 /** 4 * 设计原则:接口隔离原则 5 * 案例:安全门案例 测试类 6 */ 7 public class Client { 8 public static void main(String[] args) { 9 // 创建黑马安全门对象 10 HeimaSafetyDoor door = new HeimaSafetyDoor(); 11 // 调用功能 12 door.antiTheft(); 13 door.fireProof(); 14 door.waterProof(); 15 16 System.out.println("============"); 17 18 // 创建传智安全门对象 19 ItcastSafetyDoor itcastSafetyDoor = new ItcastSafetyDoor(); 20 // 调用功能 21 itcastSafetyDoor.antiTheft(); 22 itcastSafetyDoor.fireProof(); 23 } 24 }
迪米特法则
迪米特法则又叫最少知识原则。
只和你直接朋友交谈,不跟”陌生人“说话(Talk only to your immediate friends and not to strangers)。
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的”朋友“是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
明星与经纪人的关系案例:
明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如粉丝的见面会,和媒体公司的业务洽谈等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。
1 package com.itheima.principles.demo5; 2 3 /** 4 * 设计原则:迪米特法则 5 * 案例:明星与经纪人的关系案例 明星类 6 */ 7 public class Star { 8 private String name; 9 10 public Star(String name) { 11 this.name = name; 12 } 13 14 public String getName() { 15 return name; 16 } 17 }
1 package com.itheima.principles.demo5; 2 3 /** 4 * 设计原则:迪米特法则 5 * 案例:明星与经纪人的关系案例 粉丝类 6 */ 7 public class Fans { 8 private String name; 9 10 public Fans(String name) { 11 this.name = name; 12 } 13 14 public String getName() { 15 return name; 16 } 17 }
1 package com.itheima.principles.demo5; 2 3 /** 4 * 设计原则:迪米特法则 5 * 案例:明星与经纪人的关系案例 媒体公司类 6 */ 7 public class Company { 8 9 private String name; 10 11 public Company(String name) { 12 this.name = name; 13 } 14 15 public String getName() { 16 return name; 17 } 18 }
1 package com.itheima.principles.demo5; 2 3 /** 4 * 设计原则:迪米特法则 5 * 案例:明星与经纪人的关系案例 经济人类 6 */ 7 public class Agent { 8 private Star star; 9 private Fans fans; 10 private Company company; 11 12 public void setStar(Star star) { 13 this.star = star; 14 } 15 16 public void setFans(Fans fans) { 17 this.fans = fans; 18 } 19 20 public void setCompany(Company company) { 21 this.company = company; 22 } 23 24 // 和粉丝见面的方法 25 public void meeting() { 26 System.out.println(star.getName() + "和粉丝" + fans.getName() + "见面"); 27 } 28 29 // 和媒体公司洽谈的方法 30 public void business() { 31 System.out.println(star.getName() + "和" + company.getName() + "洽谈"); 32 } 33 34 }
1 package com.itheima.principles.demo5; 2 3 /** 4 * 设计原则:迪米特法则 5 * 案例:明星与经纪人的关系案例 测试类 6 */ 7 public class Client { 8 public static void main(String[] args) { 9 // 创建经济人类 10 Agent agent = new Agent(); 11 // 创建明星对象 12 Star star = new Star("林青霞"); 13 agent.setStar(star); 14 // 创建粉丝对象 15 Fans fans = new Fans("李四"); 16 agent.setFans(fans); 17 // 创建媒体公司对象 18 Company company = new Company("黑马媒体公司"); 19 agent.setCompany(company); 20 21 // 业务洽谈 22 agent.business(); 23 24 // 粉丝见面 25 agent.meeting(); 26 } 27 }
合成复用原则
合成复用原则是指:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然有简单和易实现的优点,但它也存在以下缺点:
继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为”白箱“复用。
子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:
它维持了类的封装性。因为成分对象内部细节是新对象看不见的,所以这种复用又称为”黑箱“复用。
对象间的耦合度低。可以在类成员位置声明抽象。
复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。
案例:汽车分类管理程序:
汽车按”动力源“可分为汽油汽车,电动汽车等;按”颜色“分类可分为白色汽车,黑色汽车,红色汽车等。如果同时考虑这两种分类,其组合就很多。