**设计模式(Design Pattern)**是软件开发过程中一般问题的解决方案,是无数面向对象软件开发人员的经验总结,对于软件设计开发十分重要。然而由于设计模式种类繁多,内容偏理论,缺乏开发经验对于相关概念的理解也比较困难,同时其中不乏很多类型相似的设计模式,更是让对设计模式的学习难上加难。
本篇为了巩固对于设计模式的理解,了解不同设计模式的特点。
资料来源如下:
设计模式辨析——工厂篇(简单工厂、静态工厂、工厂方法、抽象工厂)
对于使用到工厂的设计模式,在理解使用场景和概念后,只要记住以下几句话就可以确保掌握了。
设计模式辨析——包装篇(装饰模式、适配器模式、外观模式、代理模式)
记住关于包装篇的四种设计模式的目的:
设计模式辨析——关系篇(桥接模式、组合模式、享元模式、中介者模式)
桥接模式、组合模式和中介者模式都改变类或对象之间的关系,从而达到各自减少对象数量或降低代码复杂度的目的。
桥接模式使用合成/聚合复用原则,变继承为聚合,**将抽象部分与它的实现部分分离,**减少了子类数量,降低了代码的耦合度
组合模式用树形结构表示**“部分-整体”**的层次结构来管理对象,使得用户可以用统一的接口使用组合结构中的所有对象,简化了代码,提高了可扩展性
中介者模式使用中介者负责控制和协调一组对象间的交互,将多对多的关系转为一对多,降低了对象之间的耦合度,提升了代码的复用性、可扩展性。
享元模式则主要为了解决内存资源浪费问题,通过对对象之间相似部分的抽取,利用共享技术,减少对象实例。
区分这几种设计模式的关键,依旧在于把握目的(意图)的不同。
找出会变化的部分,把它们从不变的部分分离出来
当软件开发过程中使用接口时,代码中往往需要实例化大量的具体类,而这些实现大多是由一些条件决定的。对于如何实例化对象这个问题,往往考虑用一个单独的类来处理创造实例的过程,这个类就是工厂(factory)。
针对接口编程,不针对实现编程
定义:用于封装创建对象的代码,负责处理创建对象的细节的类被称为工厂(Factory)
优点: 将创建对象的代码集中在一个对象或方法中,避免代码中的重复,便于以后的维护
简单工厂「模式」是对于工厂最基础的应用,但它其实不能算作“工厂模式”,它不是一个设计模式,像是一种编程习惯。
通过代码实现一个简单计算器,具有加减乘除的运算功能
public class OperationFactory {
public static Operation createOperation(char operator) {
Operation operation = null;
switch (operator) { //可以看到简单工厂将运算类的实例放在工厂类中实现
//通过分支选择具体的实现类
case '+':
operation = new OperationAdd();
break;
case '-':
operation = new OperationSub();
break;
case '*':
operation = new OperationMul();
break;
case '/':
operation = new OperationDiv();
break;
default:
throw new RuntimeException("unsupported operation");
}
return operation;
}
}
public class OperationAdd extends Operation {
@Override
public double result() { //具体的操作子类只需要实现具体运算
return numberA + numberB;
}
}
public class Calculator {
public static void main(String[] args) {
Operation operation;
char operator;
operator = '+';
//使用过程中,在客户端提供具体实例的参数,传入工厂类实现
operation = OperationFactory.createOperation(operator);
operation.numberA = 1.2;
operation.numberB = 2.3;
//具体的运算过程,客户端不可见
System.out.println(operation.result());
}
}
开放—封闭原则:对扩展开放,对修改关闭
将工厂类中的创建对象的功能定义为静态的,就是静态工厂模式,它同样不是一种设计模式。
由于静态工厂与简单工厂的差别不大,在此就不详细展开。关于静态工厂模式更多的内容这篇文章中有详细描述「https://zhuanlan.zhihu.com/p/157099580」,感兴趣可以了解。
工厂方法模式定义了一个创建对象的接口,让子类决定要实例化的类是哪一个,工厂方法把一个类的实例化推迟到其子类
通过代码实现一个简单计算器,具有加减乘除的运算功能
public interface IFactory { //所有工厂的抽象,工厂接口
public Operation createOperation(); //创建实例类的方法,延迟到工厂子类中实现
}
//工厂的具体实现类
class AddFactory implements IFactory {
@Override
public Operation createOperation() { //实现对操作类的实现
return new OperationAdd();
}
}
class SubFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationSub();
}
}
class MulFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationMul();
}
}
class DivFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationDiv();
}
}
public class FactoryClient {
public static void main(String[] args) {
//由客户端选择实例化的工厂类(除法)
IFactory operFactory = new DivFactory();
Operation operation = operFactory.createOperation(); //进行运算操作
operation.numberA = 3.4;
operation.numberB = 4.5;
System.out.println(operation.result());
}
}
同:
异:
依赖倒置原则:要依赖抽象,不要依赖具体的类
避免在OO设计中违反依赖倒置原则的指导方针:
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而不需要指明它们具体的类。
抽象工厂中的每一个方法创建一个具体类,实际利用工厂方法实现
假设现在有流水线负责生产计算器按钮,并对计算器进行组装、检查,计算器按钮的颜色系列有黑色系列、白色系列等需要实现
//按钮工厂
public interface ButtonFactory{
//生产四种不同的运算按钮
public AddButton createAddButton();
public SubButton createSubButton();
public DivButton createDivButton();
public MulButton createMulButton();
}
/**
* 需要提前说明的是WhiteAddButton是AddButton的具体实现类,
* 不同的运算按钮并不是同一类,代码省略了Button相关类的声明
*/
//白色系列按钮工厂
public class WhiteButtonFactory implements ButtonFactory{
@Override
public AddButton createAddButton() {
return new WhiteAddButton();
}
@Override
public SubButton createAddButton() {
return new WhiteSubButton();
}
@Override
public DivButton createAddButton() {
return new WhiteDivButton();
}
@Override
public MulButton createAddButton() {
return new WhiteMulButton();
}
}
//黑色系列按钮工厂
public class BlackButtonFactory implements ButtonFactory{
@Override
public AddButton createAddButton() {
return new BlackAddButton();
}
@Override
public SubButton createAddButton() {
return new BlackSubButton();
}
@Override
public DivButton createAddButton() {
return new BlackDivButton();
}
@Override
public MulButton createAddButton() {
return new BlackMulButton();
}
}
//计算器类
public class Calculator() {
AddButton addButton;
DivButton divButton;
SubButton subButton;
MulButton mulButton;
public Calculator(ButtonFactory buttonFactory) {
this.buttonFactory = buttonFactory;
}
public void prepare() {
addButton = buttonFactory.createAddButton();
subButton = buttonFactory.createSubButton();
divButton = buttonFactory.createDivButton();
mulButton = buttonFactory.createMulButton();
}
//组装、测试方法
public void assembly();
public void test();
}
public abstract class AssemblyLine {
//生产方法
public Calculator produce(){
Calculator calculator;
calculator = createCalculator();
calculator.prepare();
calculator.assembly();
calculator.test();
return calculator;
}
protected abstract Calculator createCalculator();
}
//白色按钮流水线
public class WhiteAssemblyLine extends AssemblyLine {
@Override
protected Calculator createCalculator(){
Calculator calculator = null;
ButtonFactory buttonFactory = new WhiteButtonFactory();
calculator = new Calculator(buttonFactory);
return calculator;
}
}
//黑色按钮流水线
public class BlackAssemblyLine extends AssemblyLine {
@Override
protected Calculator createCalculator(String type){
Calculator calculator = null;
ButtonFactory buttonFactory = new BlackButtonFactory();
calculator = new Calculator(buttonFactory);
return calculator;
}
}
public class CalculatorClient() {
public static void main(String[] args) {
//声明流水线
AssemblyLine whiteLine = new WhiteAssemblyLine();
AssemblyLine blackLine = new BlackAssemblyLine();
//决定生成的计算器实例,可以看到系列按钮的创建过程与客户端分离
Calculator calculator1 = whiteLine.produce();
Calculator calculator2 = blackcLine.produce();
}
}
需要注意的是,由于抽象工厂类的使用场景针对「系列」,所以这里代码示例的场景发生了改变,涉及类比较多,请一定搞清楚。其中抽象工厂类的主要作用是把不同系列的按钮创建封装在了一起,因此在改变计算器的按钮系列时,我们只需要更换流水线类就好了(流水线类在这里其实只是工厂类上的一个封装,可以简单理解为更换了工厂类)。你可以设想一下,要是使用的是简单工厂、工厂方法模式,要想实现上述代码示例的功能,代码应该怎么写?改动按钮系列时,需要改动那些地方?
同:
异:
工厂方法模式:继承
抽象工厂模式:对象的组合
利用简单工厂改进抽象工厂:
反射+抽象工厂:
对于使用到工厂的设计模式,在理解使用场景和概念后,只要记住以下几句话就可以确保掌握了。
当你在编写代码时,需要扩展一个类的功能,或者是当前类的接口不能满足需求时,你会选择怎么做。重新编写子类,通过继承加入功能?修改原有类的接口使其符合现有环境?但你会发现这些改动是不完美的,它们并不符合面向对象的「开放-关闭原则」。
开放-关闭原则: 对扩展开放,对修改关闭
在软件设计模式中有一个更好的答案——包装。
下面介绍的四种设计模式都围绕着“包装”展开,那么首先先简单了解一下这些设计模式:
当你想要扩展一个类的功能时,最直接的方法就是编写一个子类,然后在子类中加入新的功能函数。但是这种更改往往会导致很多问题,例如:想要去掉父类中的一个方法时。这不是一种弹性设计,不符合「开放-关闭原则」。装饰模式提供了继承之外的一种新思路。
定义:动态地将责任附加到对象上,给对象添加额外的职责,对于扩展功能来说, 装饰者提供了比继承更有弹性的方法。
装饰者和被装饰对象需要具有相同的父类,装饰者模式用继承达到类型匹配的目的
使用方法:将需要实例化的对象传入装饰类中进行包装
http://Java.IO库就是以装饰者模式来编写的
//Component定义了一个对象接口,通过装饰类可以给这些对象动态地添加职责
public abstract class Component {
public abstract void operation();
}
/** Decorator,装饰抽象类,继承了Component
* 从外类来扩展Component类的功能,但对于Component来说,
* 是无需知道Decorator的存在的
*/
public abstract class Decorator extends Component {
protected Component component;
//获取被装饰的对象
public Component getComponent() {
return component;
}
//设置被装饰的对象
public void setComponent(Component component) {
this.component = component;
}
@Override
public void operation() {
if (component != null) {
component.operation();
}
}
}
//具体装饰类,可以为类加入新的行为
class ConcreteDecoratorA extends Decorator {
private String addedState;
@Override
public void operation() {
// 首先运行原Component的operation(),再执行本类的功能,如addedState,相当于对原Component进行了装饰
super.operation();
addedState = "A中的new state ";
System.out.println(addedState + "具体装饰对象A的操作");
}
}
class ConcreteDecoratorB extends Decorator {
@Override
public void operation() {
super.operation();
addedBehavior();
System.out.println("具体装饰对象B的操作");
}
public void addedBehavior() {
System.out.print("B中的新增行为 ");
}
}
class ConcreteDecoratorC extends Decorator {
@Override
public void operation() {
super.operation();
System.out.println("C没有特殊行为 " + "具体装饰对象C的操作");
}
}
//ConcreteComponent是定义一个具体的对象,也可以给这个对象添加一些职责
public class ConcreteComponent extends Component {
@Override
public void operation() {
System.out.println("具体对象的操作");
}
}
//装饰模式客户端调用代码
public class DecoratorClient {
public static void main(String[] args) {
ConcreteComponent concreteComponent = new ConcreteComponent();
//声明装饰类A、B、C
ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA();
ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB();
ConcreteDecoratorC concreteDecoratorC = new ConcreteDecoratorC();
//装饰的过程就像是层层包装,不断地装饰类包装对象,达到添加功能的目的
concreteDecoratorA.setComponent(concreteComponent); //装饰类A包装对象
concreteDecoratorB.setComponent(concreteDecoratorA); //装饰类B包装装饰类A(对象已经被包装在装饰类A中)
concreteDecoratorC.setComponent(concreteDecoratorB); //装饰类C包装装饰类B
concreteDecoratorC.operation();
}
}
在DecoratorClient中,经过装饰类的包装后,最终对象关系如下,被装饰者concreteComponent包装在装饰类中,同时具有了各个装饰类附加的方法和行为
各个对象的包装关系:
优点:
缺点:
定义:适配器模式将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以一起工作。
适配器通过使用对象组合,以修改的接口包装被适配者。
适配器把被适配者的接口转换为适配者的接口。
//客户所期待的接口
public abstract class Target {
public void request() {
System.out.println("普通请求!");
}
}
//适配器类,通过在内部包装一个Adaptee对象,把原接口转换成目标接口
public class Adapter extends Target {
//内部的Adaptee对象
private Adaptee adaptee = new Adaptee();
@Override
public void request() { //适配器类提供的客户端需要的接口
adaptee.specificRequest();
}
}
//需要适配的类
public class Adaptee {
public void specificRequest() {
System.out.println("特殊的请求!");
}
}
//适配器客户端
public class AdapterClient {
public static void main(String[] args) {
Target target;
target = new Adapter(); //直接调用适配器类,内部包装了Adaptee
target.request();
}
}
优点:
缺点:
适配器模式只有在碰到无法改变原有设计和代码的情况才考虑,在设计开发的过程中,应该预先预防接口不匹配的问题发生,当出现有小的接口不统一时,应及时重构,避免问题的扩大
类适配器通过多重继承对一个接口与另一个接口进行匹配
优点:
缺点:
可以适配某个类及其任何子类
优点:
缺点:
装饰者模式:需要添加新的功能时使用
适配器模式:需要转换接口时使用
最少知识原则(Least Knowledge Principle)又名迪米特法则(Law of Demeter)
- 每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元,即只和你的密友谈话
方针:只调用属于以下范围的方法
- 该对象本身
- 被当做方法的参数而传递进来的对象
- 此方法所创建或实例化的任何对象
- 对象的任何组件
当有很多复杂的接口需要使用时,通过一个类来将复杂的逻辑封装在内并提供简单的统一接口,可以很好的提高代码的可读性,降低程序复杂度。
外观模式就是这样的一个类,不过它并没有“封装”子系统。当你需要简化并统一一个很大的接口或一群复杂的接口时,可以使用外观模式。
定义:外观模式提供了一个统一的接口,用来访问子系统中的一组接口。此模式定义了一个高层接口,使得子系统更容易使用。
通过使用外观模式,在数据访问层、业务逻辑层和表示层的层与层之间建立“外观”,降低耦合度
//“系统”接口,只是标记接口,暂无任何意义
public interface SystemInterface {
}
//子系统类
class SubSystemOne implements SystemInterface {
public void methodOne() {
System.out.println("子系统方法一");
}
}
class SubSystemTwo implements SystemInterface {
public void methodTwo() {
System.out.println("子系统方法二");
}
}
class SubSystemThree implements SystemInterface {
public void methodThree() {
System.out.println("子系统方法三");
}
}
class SubSystemFour implements SystemInterface {
public void methodFour() {
System.out.println("子系统方法四");
}
}
//外观类,包括了所有的子系统的方法或属性,进行组合,以备外界调用
public class Facade {
//子系统
SubSystemOne subSystemOne;
SubSystemTwo subSystemTwo;
SubSystemThree subSystemThree;
SubSystemFour subSystemFour;
public Facade() {
subSystemOne = new SubSystemOne();
subSystemTwo = new SubSystemTwo();
subSystemThree = new SubSystemThree();
subSystemFour = new SubSystemFour();
}
//外观类提供的统一接口
public void methodA() {
System.out.println("方法组A:");
subSystemOne.methodOne();
subSystemTwo.methodTwo();
subSystemFour.methodFour();
}
public void methodB() {
System.out.println("方法组B:");
subSystemThree.methodThree();
subSystemFour.methodFour();
}
}
//外观类客户端
public class FacadeClient {
public static void main(String[] args) {
// 由于Facade的作用,客户端可以根本不知道四个子系统的存在
Facade facade = new Facade();
facade.methodA();
facade.methodB();
}
}
维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,此时可以为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
优点:
缺点:
代理从字面意思理解就是代理他人的职务。在计算机中,代理常用来控制和管理访问,如代理服务器,可以作为中转站,代理网络用户去获取网络信息。
定义:代理模式为另一个对象提供一个替身或占位符以便控制客户对对象的访问
这里的代理只是简单的示例,实际上的代理往往在上面提到的几个场景(远程、虚拟、保护)中使用,用来控制和管理访问
//接口类,定义真实实体类与代理类共用的接口
public interface Subject {
public void request();
}
//实体类
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实对象的请求");
}
}
//代理类
public class Proxy implements Subject {
// 保存一个引用,使得代理可以访问真实实体
Subject subject;
public Proxy() {
subject = new RealSubject();
}
@Override
public void request() {
subject.request();
}
}
//代理客户端
public class ProxyClient {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}
优点:
缺点:
对于装饰模式、适配器模式、外观模式和代理模式,他们彼此之间都有相似之处,例如四种都对对象进行了包装,适配器模式和外观模式都提供了接口,代理模式和装饰模式都可能会引入新功能…
但其实区分这几种设计模式重点不在于如何包装类,包装类的个数、是否添加新功能,重点在于不同设计模式的目的(意图)不同
装饰模式:将一个对象包装起来以增加新的行为和责任
适配器模式:将一个对象包装起来以改变其接口
外观模式:将一群对象包装起来以简化其接口
代理模式:将一个对象包装起来以控制对它的访问
把握了以上几点,也就记住了四种设计模式的本质区别,相信对于每个设计模式适用场景也能有更深的理解。
在上部分中,针对继承在编译时静态决定类,可扩展性差等问题,装饰模式提供了比继承更弹性的方法来为类添加功能函数,这部分进一步讨论继承的潜在问题,重点着眼于类和对象之间的关系。桥接模式、组合模式和享元模式都属于 「结构型设计模式」, 而中介者模式属于 「行为型设计模式」 ,为了更好的几种模式对比而放在这一部分中,尽管关系篇的命名可能不够准确,但还是比较能概括这几种设计模式的共同点。
继承作为面向对象的三个基本特征之一,是最常见的一种类关系,作用如下:
优点:继承可以使用子类重写父类方法的方式进行扩展,提高了代码的复用性,并获得了一定的可扩展性
缺点:继承的过度使用会导致类的结构过于复杂,对象之间关系太多,代码难以维护,扩展性差。
子类和父类之间的关系是一种高耦合关系。
一个父类可以通过子类实现多种变化。在类变化较为复杂的情况下,只使用继承会造成大量的类增加,不能满足开放-封闭原则。继承是一种强耦合的结构,我们的设计目标是找到一个弱耦合、松耦合的结构来表示抽象类和实现之间的关系,这个设计模式就是桥接模式。
桥接模式通过解耦类不同方向的变化,使用对象组合的方式,把两个角色之间继承关系改为组合关系,从而使得两者应对各自独立的变化。
顾名思义,「桥」 起到连接作用,在桥接模式中,通过 「桥」 连接的抽象和实现两个独立的结构,这两个部分可以以继承的方式独立扩展、变化而不相互影响。
定义:桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接模式的核心意图:将每一种分类的实现分离出来,让他们独立变化,减少它们之间的耦合,每种实现的变化不会影响其他实现,从而达到应对变化的目的。
尽量使用合成/聚合,尽量不要使用类继承
合成(Composition):表示一种强“拥有”关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样
聚合(Aggregation):表示一种弱“拥有”关系,体现A对象包含B对象,但B对象不是A对象的一部分
合成和聚类都是关联的特殊种类。
好处:
假设现在有一个书本生产系统,生产的书本有Book1、Book2两种类型,黄色和绿色两种不同颜色,现有抽象类Book,若仅使用继承,那么就需要如下图示的四种子类才可以满足需求。
若要添加书本类型或颜色类型,均需要声明对子类进行新的扩展,需要的子类数将呈现爆炸性增长,代码复杂度将不断增加。
仅使用继承:
而使用桥接模式,可以将书本类型(抽象)和颜色类型(实现)两部分分离开来,改继承为组合,扩展和变化将在两个独立角度中进行,不再需要通过添加子类的方式实现扩展。
桥接模式:
颜色相关的代码将被抽取到颜色类中,并通过在书本类中添加指向颜色对象的成员变量,将书本和颜色两部分连接起来。使用桥接模式后新增颜色将不会对现有的形状类产生影响,反之新增形状也不会对现有的颜色类产生影响,新增功能或方法都只需要扩展类即可,不需要修改现有类,符合“开放-封闭”原则。
public abstract class Abstraction {
protected Implementor implementor;// 桥接模式的关键,使得Abstraction聚合Implementor
private String name;
public Abstraction(String name) {
this.setName(name);
}
//抽象类所聚合的实现类
public void setImplementor(Implementor implementor) {
this.implementor = implementor;
}
//相关方法
public void operation() {
System.out.print("Abstraction-" + this.getName() + ": ");
implementor.operation();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//抽象实现类A
class AbstractionA extends Abstraction {
public AbstractionA(String name) {
super(name);
}
@Override
public void operation() {
super.operation();
}
}
//抽象实现类B
class AbstractionB extends Abstraction
public AbstractionB(String name) {
super(name);
}
@Override
public void operation() {
super.operation();
}
}
public abstract class Implementor {
public abstract void operation();
}
class ConcreteImplemtorA extends Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplemtorA的方法执行");
}
}
class ConcreteImplemtorB extends Implementor {
@Override
public void operation() {
System.out.println("ConcreteImplemtorB的方法执行");
}
}
//客户端
public class BridgeClient {
public static void main(String[] args) {
Abstraction a = new AbstractionA("A");//抽象类A实例
a.setImplementor(new ConcreteImplemtorA());//设置包含的具体实现类
a.operation();
a.setImplementor(new ConcreteImplemtorB());
a.operation();
Abstraction b = new AbstractionB("B");//抽象类B实例
b.setImplementor(new ConcreteImplemtorA());
b.operation();
b.setImplementor(new ConcreteImplemtorB());
b.operation();
}
}
优点:
缺点:
定义:将对象组合成树形结构以表示 “部分-整体” 的层次结构,组合模式能让用户以一致的方法处理单个对象和组合对象。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。任何用到基本对象的地方都可以使用组合对象,客户可以一致地使用组合结构和单个对象。
组合由组件构成,组合结构中的任意对象称为组件,组件有两种类型:
组合的树形结构:
根是顶层的组合,往下是枝节点,最末端是叶节点
在组件接口中声明所有用来管理子对象的方法,使子对象(叶节点和枝节点)对于外界没有区别,具备完全一致的行为接口。
透明性:组合模式以单一责任设计原则换取“透明性”,组件的接口同时包含一些管理子节点和叶节点的操作,无论是子节点还是叶节点对用户而言都是透明的
存在的问题:
在组合对象中实现所有用来管理子对象的方法,组件类中不声明相关方法,客户端调用时需要做出相应的判断
安全性: 将责任进行区分,放在不同的接口上,代码需要通过条件语句等判断处理不同类型的节点,失去了透明性,但这样的设计比较安全
安全方式符合设计模式的的单一职责原则和接口隔离原则
存在的问题:
总结:在实现组合模式时,需要根据需要平衡透明性和安全性。
/**
* Component为组合中的对象提供统一的接口,在适当情况下,实现所有类共有接口的默认行为。
* 使用组合结构的所有类都继承该类
*/
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract void display(int depth);
}
// 组合类(枝节点),用来存储子部件,实现了组件接口中的相关操作
public class Composite extends Component {
private List<Component> children = new ArrayList<Component>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public void display(int depth) {
// 显示其枝节点名称,并对其下级进行遍历
System.out.println(StringUtil.repeatableString("-", depth) + this.name);
for (Component component : children) {
component.display(depth + 2);
}
}
}
//叶节点类,组合模式中的最小粒度,没有子节点
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void add(Component component) {
System.out.println("cannot add to a leaf");
}
@Override
public void remove(Component component) {
System.out.println("cannot remove from a leaf");
}
@Override
public void display(int depth) {
// 通过“-”的数目显示级别
System.out.println(StringUtil.repeatableString("-", depth) + this.name);
}
}
public class CompositeClient {
// 展现了组合结构的构成过程
public static void main(String[] args) {
// 生成树根,根上长出两叶Leaf A和Leaf B
Composite root = new Composite("root");
root.add(new Leaf("Leaf A"));
root.add(new Leaf("Leaf B"));
// 根上长出分支Composite X,分支上也有两叶Leaf X-A和Leaf X-B
Composite compositeX = new Composite("Composite X");
compositeX.add(new Leaf("Leaf X-A"));
compositeX.add(new Leaf("Leaf X-B"));
root.add(compositeX);
// 在Composite X上再长出分支Composite X-Y,分支上也有两叶Leaf X-Y-A和Leaf X-Y-B
Composite compositeXY = new Composite("Composite X-Y");
compositeXY.add(new Leaf("Leaf X-Y-A"));
compositeXY.add(new Leaf("Leaf X-Y-B"));
compositeX.add(compositeXY);
// 显示大树的样子
root.display(1);
}
}
优点:
缺点:
在程序设计中,有时需要使用大量对象,但使用很多对象会导致的内存开销过大的问题,若使用的对象大量都是重复,则会造成资源的浪费。享元模式是针对这一问题提出的,可以避免大量相似类的开销。
运用共享技术有效地支持大量细粒度的对象
在享元对象内部且不会随环境改变而改变的共享部分
随着环境改变而改变,不可以共享的状态
用来创建并管理Flyweight对象,主要用来确保合理地共享Flyweight,当用户请求一个Flyweight时,为对象提供一个已创建的实例,若不存在则为对象创建一个新的Flyweight实例
所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态
继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间
指不需要共享的Flyweight子类
//享元超类
public abstract class FlyWeight {
//接受并作用于外部状态的方法
public abstract void operation(int extrinsicState);
}
class ConcreteFlyWeight extends FlyWeight {
@Override
public void operation(int extrinsicState) {
System.out.println("具体FlyWeight:" + extrinsicState);
}
}
class UnsharedConcreteFlyWeight extends FlyWeight {
@Override
public void operation(int extrinsicState) {
System.out.println("不共享的具体FlyWeight:" + extrinsicState);
}
}
public class FlyWeightFactory {
private HashMap<String, FlyWeight> flyWeights = new HashMap<String, FlyWeight>();
public FlyWeight getFlyWeight(String key) {
if (!flyWeights.containsKey(key)) {
flyWeights.put(key, new ConcreteFlyWeight());
}
return flyWeights.get(key);
}
}
public class FlyWeightClient {
public static void main(String[] args) {
int extrinsicState = 22;
FlyWeightFactory f = new FlyWeightFactory();
FlyWeight fx = f.getFlyWeight("X");
fx.operation(--extrinsicState);
FlyWeight fy = f.getFlyWeight("Y");
fy.operation(--extrinsicState);
FlyWeight fz = f.getFlyWeight("Z");
fz.operation(--extrinsicState);
FlyWeight uf = new UnsharedConcreteFlyWeight();
uf.operation(--extrinsicState);
}
}
优点:
缺点:
当程序设计中使用大量对象时,对象之间的关联关系和系统代码逻辑复杂,可扩展性差,不利于应对变化。中介者模式通过对对象集体行为的封装来避免这个问题。
定义:用一个中介对象来封装一系列的对象交互。中介者使个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式将集体行为封装成一个独立的中介者对象
实现业务的具体类,各同事类之间的行为均通过中介者进行控制、协调。
多对象关系图:
中介者模式对象关系图:
中介者模式使得对象均只需与中介者沟通,对象之间的关系转变为星型结构,能有效地减少系统的耦合
//抽象中介类
public abstract class Mediator {
public abstract void send(String message, Colleague colleague);
}
//具体中介类
class ConcreteMediator extends Mediator {
// 需要了解所有的具体同事对象
private ConcreteColleague1 c1;
private ConcreteColleague2 c2;
public ConcreteColleague1 getC1() {
return c1;
}
public void setC1(ConcreteColleague1 c1) {
this.c1 = c1;
}
public ConcreteColleague2 getC2() {
return c2;
}
public void setC2(ConcreteColleague2 c2) {
this.c2 = c2;
}
@Override
public void send(String message, Colleague colleague) {
// 重写发送信息的方法,根据对象做出选择判断,通知对象
if (colleague == c1) {
c2.notifyMsg(message);
} else {
c1.notifyMsg(message);
}
}
}
//抽象同事类
public abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public abstract void sendMsg(String message);
public abstract void notifyMsg(String message);
}
//具体同事类
class ConcreteColleague1 extends Colleague {
public ConcreteColleague1(Mediator mediator) {
super(mediator);
}
@Override
public void sendMsg(String message) {
mediator.send(message, this);
}
@Override
public void notifyMsg(String message) {
System.out.println("同事1得到消息:" + message);
}
}
class ConcreteColleague2 extends Colleague {
public ConcreteColleague2(Mediator mediator) {
super(mediator);
}
@Override
public void sendMsg(String message) {
mediator.send(message, this);
}
@Override
public void notifyMsg(String message) {
System.out.println("同事2得到消息:" + message);
}
}
public class MediatorClient {
public static void main(String[] args) {
ConcreteMediator concreteMediator = new ConcreteMediator();
// 让两个具体同事类认识中介者对象
ConcreteColleague1 concreteColleague1 = new ConcreteColleague1(
concreteMediator);
ConcreteColleague2 concreteColleague2 = new ConcreteColleague2(
concreteMediator);
// 让中介者认识各个具体同事类对象
concreteMediator.setC1(concreteColleague1);
concreteMediator.setC2(concreteColleague2);
// 具体同事类对象的消息发送都是通过中介者对象转发
concreteColleague1.sendMsg("吃过饭了没有?");
concreteColleague2.sendMsg("没有呢,你打算请客?");
}
}
优点:
缺点:
中介者模式的优点来自集中控制,缺点也来自集中控制
中介者实现了控制集中化,将交互复杂性转变为中介者的复杂性,当同事类越多,中介者的业务就越复杂,代码会变得难以管理和改动。
中介者模式一般应用于一组对象以定义良好但复杂的方式进行通信的场合,以及想定制一个分布在多个类中的行为,而不想生成太多子类的场合
桥接模式、组合模式和中介者模式都改变类或对象之间的关系,从而达到各自减少对象数量或降低代码复杂度的目的。
桥接模式使用合成/聚合复用原则,变继承为聚合, 将抽象部分与它的实现部分分离, 减少了子类数量,降低了代码的耦合度
组合模式用树形结构表示 “部分-整体” 的层次结构来管理对象,使得用户可以用统一的接口使用组合结构中的所有对象,简化了代码,提高了可扩展性
中介者模式使用中介者负责控制和协调一组对象间的交互,将多对多的关系转为一对多,降低了对象之间的耦合度,提升了代码的复用性、可扩展性。
享元模式则主要为了解决内存资源浪费问题,通过对对象之间相似部分的抽取,利用共享技术,减少对象实例。
区分这几种设计模式的关键,依旧在于把握目的(意图)的不同。