您的位置:首页 > 汽车 > 时评 > 可画在线设计网站_抖音投放广告价格一览_东莞seo优化seo关键词_百度权重划分等级

可画在线设计网站_抖音投放广告价格一览_东莞seo优化seo关键词_百度权重划分等级

2025/4/6 10:57:29 来源:https://blog.csdn.net/LYX3693/article/details/146971615  浏览:    关键词:可画在线设计网站_抖音投放广告价格一览_东莞seo优化seo关键词_百度权重划分等级
可画在线设计网站_抖音投放广告价格一览_东莞seo优化seo关键词_百度权重划分等级

一、七大原则

1、单一职责原则

1.1、概念描述

对类来说的,即一个类应该只负责一项职责。如果一个类负责两个职责,可能存在职责1变化,引起职责2变化的情况。可以基于抽象逻辑,或者业务逻辑对类进行细化。

1.2、案例演示

这里基于方法和类的细化都可以,可以根据实际业务选择。

class Animal {public void dogVoice (){System.out.println("狗叫声:旺旺");}public void cowVoice (){System.out.println("牛叫声:哞哞");}
}
class DogVoice {public String getDogVoice (){return "旺旺" ;}
}
class CowVoice {public String getCowVoice (){return "哞哞" ;}
}

1.3、注意事项

减少代码一处变更引起的程序大规模改动情况,降低类的复杂度,提高类的可读性,可维护性。通常情况下,需要遵守单一职责原则,可以适当违反单一职责原则。

2、接口隔离原则

2.1、概念描述

客户端不应该依赖它不需要的接口,一个类对另一个类的依赖,应该建立在最小的接口上。

2.2、案例演示

interface ReadBlog {String getBlog () ;
}
interface AdminBlog {Boolean insertBlog () ;Boolean updateBlog () ;Boolean deleteBlog () ;
}
/*** 读者只开放博客阅读接口*/
class Reader implements ReadBlog {@Overridepublic String getBlog() {return null;}
}
/*** 管理员有博客全部的管理权限*/
class AdminUser implements AdminBlog,ReadBlog {@Overridepublic String getBlog() {return null;}@Overridepublic Boolean insertBlog() {return null;}@Overridepublic Boolean updateBlog() {return null;}@Overridepublic Boolean deleteBlog() {return null;}
}

2.3、注意事项

接口的设计粒度越小,则应用系统程序越灵活,程序变得灵活也就意味同时结构复杂性提高,开发开发和理解的难度也会变大,可维护性降低。

3、依赖倒转原则

3.1、概念描述

高层模块不应该依赖低层模块,两者应依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;中心思想是面向接口编程。

3.2、案例演示

public class C01_FarmFactory {public static void main(String[] args) {Animal animal = new Dog() ;FarmFactory farm = new Farming() ;farm.breed(animal) ;animal = new Pig() ;farm.breed(animal) ;}
}
/*** 接口声明依赖对象*/
interface FarmFactory {void breed (Animal animal) ;
}
class Farming implements FarmFactory {@Overridepublic void breed(Animal animal) {System.out.println("农场饲养:"+animal.getAnimalName());}
}
interface Animal {String getAnimalName () ;
}
class Dog implements Animal {@Overridepublic String getAnimalName() {return "牧羊犬";}
}
class Pig implements Animal {@Overridepublic String getAnimalName() {return "土猪一号";}
}

3.3、注意事项

相对于系统开发的多变性,抽象的相对稳定。以抽象为基础搭建的架构比以细节为基础的架构要稳定灵活。下层模块尽量都要有抽象类或接口,程序稳定性更好。变量的声明类型尽量是抽象类或接口,这样变量引用和实际对象之间存在一个过渡空间,利于程序扩展和优化。

4、里氏替换原则

4.1、概念描述

假设如下场景:

  • 存在,一个类型T1,和实例的对象O1

  • 存在,一个类型T2,和实例的对象O2

如果将所有类型为T1的对象O1都替换成类型T2的对象O2,程序的行为不发生改变。那么类型T2是类型T1的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。

4.2、案例演示

public class C01_Calculate {public static void main(String[] args) {BizCalculate bizCalculate = new BizCalculate() ;System.out.println(bizCalculate.add(2,3));}
}
class Calculate { }
class BaseCalculate extends Calculate {public int add (int a,int b){return a+b;}
}
/*** 这里使用组合的方式完成计算*/
class BizCalculate extends Calculate {private BaseCalculate baseCalculate = new BaseCalculate() ;public int add (int a,int b){return this.baseCalculate.add(a,b);}
}

4.3、注意事项

使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法;子类可以扩展父类的功能,但不能改变原有父类的功能;在适当的情况下,可以通过聚合,组合,依赖等方式解决问题。

5、开闭原则

5.1、概念描述

开闭原则是编程中最基础、最重要的设计原则,在代码结构的设计设计时,应该考虑对扩展开放,对修改关闭,抽象思维搭建结构,具体实现扩展细节。

5.2、案例演示

public class C01_BookPrice {public static void main(String[] args) {ParityBook parityBook = new DiscountBook("Java",100.00) ;System.out.println(parityBook.getPrice());}
}
interface Book {String getName () ;Double getPrice () ;
}
/*** 平价书籍*/
class ParityBook implements Book {private String name ;private Double price ;public ParityBook(String name, Double price) {this.name = name;this.price = price;}@Overridepublic String getName() {return this.name ;}@Overridepublic Double getPrice() {return this.price ;}
}
/*** 打折数据扩展价格计算策略*/
class DiscountBook extends ParityBook {public DiscountBook(String name, Double price) {super(name, price);}@Overridepublic Double getPrice() {double oldPrice = super.getPrice();return oldPrice * 0.8 ;}
}

5.3、注意事项

基于开闭原则设计的代码结构可以提高复用性和可维护性,通过接口或抽象类可以约束类的变化行为,基于指定策略对变化行为进行封装,并且能够实现对扩展开放,使用设计模式的基本原则就是遵循开闭原则。

6、迪米特原则

6.1、概念描述

迪米特原则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外开放任何信息。类与类关系越密切,耦合度越大,耦合的方式很多,依赖,关联,组合,聚合等。

  • 直接朋友概念

两个对象之间有耦合关系,就说这两个对象之间是朋友关系。其中出现成员变量,方法参数,方法返回值中的类称为直接朋友,而出现在局部变量中的类不是直接朋友。从原则上说,陌生的类最好不要以局部变量的形式出现在类的内部。

6.2、案例演示

public class C01_Employee {public static void main(String[] args) {HeadCompanyEmpManage empManage = new HeadCompanyEmpManage() ;BranchCompanyEmpManage branchEmp = new BranchCompanyEmpManage() ;empManage.printEmp(branchEmp);}
}
/*** 总公司员工*/
class HeadCompanyEmp {public String name ;public HeadCompanyEmp(String name) {this.name = name;}@Overridepublic String toString() {return "HeadCompanyEmp{name='" + name + '}';}
}
/*** 分公司员工*/
class BranchCompanyEmp {public String name ;public BranchCompanyEmp(String name) {this.name = name;}@Overridepublic String toString() {return "BranchCompanyEmp{name='" + name + '}';}
}
/*** 分公司员工管理*/
class BranchCompanyEmpManage {// 添加分公司员工public List<BranchCompanyEmp> addEmp (){List<BranchCompanyEmp> list = new ArrayList<>() ;for (int i = 1 ; i <= 3 ; i++){list.add(new BranchCompanyEmp("分公司员工"+i)) ;}return list ;}// 获取分公司员工public void printBranchCompanyEmp (){List<BranchCompanyEmp> list = addEmp () ;for (BranchCompanyEmp emp:list){System.out.println(emp);}}
}
/*** 总公司员工管理,基于迪米特原则,不出现陌生类*/
class HeadCompanyEmpManage {// 添加总公司员工public List<HeadCompanyEmp> addHeadEmp (){List<HeadCompanyEmp> list = new ArrayList<>() ;for (int i = 1 ; i <= 3 ; i++){list.add(new HeadCompanyEmp("总公司员工"+i)) ;}return list ;}public void printEmp (BranchCompanyEmpManage empManage){// 打印分公司员工empManage.printBranchCompanyEmp();List<HeadCompanyEmp> headEmpList = addHeadEmp () ;for (HeadCompanyEmp headCompanyEmp:headEmpList){System.out.println(headCompanyEmp);}}
}

6.3、注意事项

迪米特原则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此可以降低耦合关系。降低耦合关系,并不是要求完全没有依赖关系,过度的使用迪米特原则,容易产生大量的中间类,导致复杂度变大。所以在使用迪米特原则时要根据实际业务权衡。

7、设计原则总结

设计模式和设计原则的核心思想都是:判断业务应用中可能会变化模块,并且把这些模块独立出来,基于指定的策略进行封装,不要和那些变化的不大的模块耦合在一起,封装思想上基于接口和抽象类,而不是针对具体的实现编程。核心目的就是降低交互对象之间的松耦合度。设计模式和原则都不是可以生搬硬套的公式,个人理解:只要形似,神韵就自然不差。

8、设计模式的分类


总体来说设计模式分为三大类:

二、创建型模式

创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。

创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。

1 单例模式

        保证一个类仅有一个实例,并提供一个访问它的全局访问点,单例模式是关于怎样设计一个类,并使得该类只有一个实例的成熟模式,该模式的关键是将类的构造方法设置为private权限,并提供一个返回它的唯一实例的类方法。

/*** 单例设计模式* <p>*     单例设计模式核心思想:私有构造方法,自己创建实例,对外提供获取实例方法。*     1.饿汉式{@link LazySingleton}:*     2.懒汉式{@link HungerSingleton}:* </p>*/
public class Singleton {/*** 饿汉式*/static class LazySingleton{//创建实例private static class Inner{private static LazySingleton sSingleton = new LazySingleton();}//私有构造private LazySingleton() {}//对外提供获取实例方法public static LazySingleton getInstance() {return Inner.sSingleton;}}/*** 懒汉式*/static class HungerSingleton{private static HungerSingleton sSingleton = null;private HungerSingleton() {}public static HungerSingleton getInstance() {if (sSingleton == null) {synchronized (HungerSingleton.class){if (sSingleton == null) {sSingleton = new HungerSingleton();}}}return sSingleton;}}public static void main(String[] args) {LazySingleton inner =LazySingleton.getInstance();HungerSingleton hungerSingleton =HungerSingleton.getInstance();}
}

单例模式优缺点

优点:

  •  你可以保证一个类只有一个实例。
  •  你获得了一个指向该实例的全局访问节点。
  •  仅在首次请求单例对象时对其进行初始化。

缺点:

  •  违反了单一职责原则。 该模式同时解决了两个问题。
  •  单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
  •  该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
  •  单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。

 与其他模式的关系

  • 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。

  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。

    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。
  • 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。

2 工厂模式

工厂模式主要负责将大量有共同接口的类实例化,可以动态的决定创建哪一个类,而不事先知道要实例化具体哪一个类。

  • 简单工厂模式:简单工厂模式是类的创建模式,又称为静态工厂方法,是有一个工厂的对象决定创建哪一类具体的产品。

  • 工厂模式:工厂模式又成为动态工厂模式,由于静态工厂模式的缺点是,有新的类加入时需要修改工厂类代码,不符合开闭原则,因此引入类动态工厂模式,使用多态的方式去创建,具体创建类的过程交给子类去做,工厂和产品之间是一种平级的等级结构。

  • 抽象工厂模式:抽象工厂模式是工厂模式中最具一般性的一种形态,提供一个创建一系列或相互依赖对象的接口,而无须制定他们具体的类。 它与工厂模式的最大区别是:工厂模式面对一个产品等级的结构,而抽象工厂模式面对多个产品等级结构。


/**
* 工厂方法模式
* <p>
*     工厂方法模式主要分为三种:
*     1.普通工厂模式{@link FunctionCommonFactory}:建立一个工厂类,对实现了同一接口的类的实例创建,仅提供一个生产方法,根据不同标记位创建不同实例。
*     2.多个工厂模式{@link FunctionMoreFactory}:建立一个工厂类,对实现了同一接口的类的实例创建,提供创建不同实例的多个方法(对1的改进)。
*     3.静态工厂模式{@link FunctionStaticFactory}:建立一个工厂类,对实现了同一接口的类的实例创建,提供创建不同实例的多个静态方法(对2的改进)。
* </p>
*/
public class FactoryMethod {/*** 共同功能接口*/interface Function{void play();}/*** LOL游戏实现功能接口*/static class Lol implements Function{@Overridepublic void play() {System.out.println("Summoner! Please select your hero!");}}/*** 荒野行动游戏实现功能接口*/static class KnivesOut implements Function{@Overridepublic void play() {System.out.println("LYB");}}/*** 普通工厂类*/static class FunctionCommonFactory{public static final int LOL = 0;public static final int KNO = 1;public Function produce(int flag) {switch (flag) {case LOL:return new Lol();case KNO:return new KnivesOut();}return null;}}/*** 多个工厂类*/static class FunctionMoreFactory{public Function produceLol() {return new Lol();}public Function produceKno() {return new KnivesOut();}}/*** 静态工厂类*/static class FunctionStaticFactory{public static Function produceLol() {return new Lol();}public static Function produceKno() {return new KnivesOut();}}/*** 测试* @param args*/public static void main(String[] args) {commonFactory();moreFactory();staticFactory();}/*** 静态工厂测试*/private static void staticFactory() {Function lol = FunctionStaticFactory.produceLol();Function kno = FunctionStaticFactory.produceKno();lol.play();kno.play();}/*** 多个工厂测试*/private static void moreFactory() {FunctionMoreFactory moreFactory = new FunctionMoreFactory();Function lol = moreFactory.produceLol();Function kno = moreFactory.produceKno();lol.play();kno.play();}/*** 普通工厂测试*/private static void commonFactory() {FunctionCommonFactory commonFactory = new FunctionCommonFactory();Function lol = commonFactory.produce(FunctionCommonFactory.LOL);Function kno = commonFactory.produce(FunctionCommonFactory.KNO);lol.play();kno.play();}
}

 工厂方法模式适合应用场景

         当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。

 工厂方法将创建产品的代码与实际使用产品的代码分离, 从而能在不影响其他代码的情况下扩展产品创建部分代码。

例如, 如果需要向应用中添加一种新产品, 你只需要开发新的创建者子类, 然后重写其工厂方法即可。

         如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。

 继承可能是扩展软件库或框架默认行为的最简单方法。 但是当你使用子类替代标准组件时, 框架如何辨识出该子类?

解决方案是将各框架中构造组件的代码集中到单个工厂方法中, 并在继承该组件之外允许任何人对该方法进行重写。

让我们看看具体是如何实现的。 假设你使用开源 UI 框架编写自己的应用。 你希望在应用中使用圆形按钮, 但是原框架仅支持矩形按钮。 你可以使用 圆形按钮Round­Button子类来继承标准的 按钮Button类。 但是, 你需要告诉 UI框架UIFramework类使用新的子类按钮代替默认按钮。

为了实现这个功能, 你可以根据基础框架类开发子类 圆形按钮 UIUIWith­Round­Buttons , 并且重写其 create­Button创建按钮方法。 基类中的该方法返回 按钮对象, 而你开发的子类返回 圆形按钮对象。 现在, 你就可以使用 圆形按钮 UI类代替 UI框架类。 就是这么简单!

         如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。

 在处理大型资源密集型对象 (比如数据库连接、 文件系统和网络资源) 时, 你会经常碰到这种资源需求。

让我们思考复用现有对象的方法:

  1. 首先, 你需要创建存储空间来存放所有已经创建的对象。
  2. 当他人请求一个对象时, 程序将在对象池中搜索可用对象。
  3. … 然后将其返回给客户端代码。
  4. 如果没有可用对象, 程序则创建一个新对象 (并将其添加到对象池中)。

这些代码可不少! 而且它们必须位于同一处, 这样才能确保重复代码不会污染程序。

可能最显而易见, 也是最方便的方式, 就是将这些代码放置在我们试图重用的对象类的构造函数中。 但是从定义上来讲, 构造函数始终返回的是新对象, 其无法返回现有实例。

因此, 你需要有一个既能够创建新对象, 又可以重用现有对象的普通方法。 这听上去和工厂方法非常相像。

 实现方式

  1. 让所有产品都遵循同一接口。 该接口必须声明对所有产品都有意义的方法。

  2. 在创建类中添加一个空的工厂方法。 该方法的返回类型必须遵循通用的产品接口。

  3. 在创建者代码中找到对于产品构造函数的所有引用。 将它们依次替换为对于工厂方法的调用, 同时将创建产品的代码移入工厂方法。

    你可能需要在工厂方法中添加临时参数来控制返回的产品类型。

    工厂方法的代码看上去可能非常糟糕。 其中可能会有复杂的 switch分支运算符, 用于选择各种需要实例化的产品类。 但是不要担心, 我们很快就会修复这个问题。

  4. 现在, 为工厂方法中的每种产品编写一个创建者子类, 然后在子类中重写工厂方法, 并将基本方法中的相关创建代码移动到工厂方法中。

  5. 如果应用中的产品类型太多, 那么为每个产品创建子类并无太大必要, 这时你也可以在子类中复用基类中的控制参数。

    例如, 设想你有以下一些层次结构的类。 基类 邮件及其子类 航空邮件和 陆路邮件 ; ​ 运输及其子类 飞机卡车和 火车 。 ​ 航空邮件仅使用 飞机对象, 而 陆路邮件则会同时使用 卡车和 火车对象。 你可以编写一个新的子类 (例如 火车邮件 ) 来处理这两种情况, 但是还有其他可选的方案。 客户端代码可以给 陆路邮件类传递一个参数, 用于控制其希望获得的产品。

  6. 如果代码经过上述移动后, 基础工厂方法中已经没有任何代码, 你可以将其转变为抽象类。 如果基础工厂方法中还有其他语句, 你可以将其设置为该方法的默认行为。

 工厂方法模式优缺点

优点:

  •  你可以避免创建者和具体产品之间的紧密耦合。
  •  单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
  •  开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。

缺点:

  •  应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
 与其他模式的关系
  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

  • 你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器, 并使得迭代器与集合相匹配。

  • 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

  • 工厂方法是模板方法模式的一种特殊形式。 同时, 工厂方法可以作为一个大型模板方法中的一个步骤。

3 抽象工厂模式

抽象工厂模式:抽象工厂模式是工厂模式中最具一般性的一种形态,提供一个创建一系列或相互依赖对象的接口,而无须制定他们具体的类。 它与工厂模式的最大区别是:工厂模式面对一个产品等级的结构,而抽象工厂模式面对多个产品等级结构。

/**
* 抽象工厂模式(对工厂方法模式的改进)
* <p>
*     工厂方法模式有一个问题就是,类的创建依赖工厂类,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则;所以,从设计角度考虑,有一定的问题。
*     而抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。
* </p>
*/
public class AbstractFactory {/*** 共同功能接口*/interface Function{void play();}/*** 工厂共同生产接口*/interface Provider {Function produce();}/*** LOL游戏实现功能接口*/static class Lol implements Function {@Overridepublic void play() {System.out.println("Summoner! Please select your hero!");}}/*** 荒野行动游戏实现功能接口*/static class KnivesOut implements Function {@Overridepublic void play() {System.out.println("LYB");}}/*** LOL工厂类*/static class LolFactory implements Provider{@Overridepublic Function produce() {return new Lol();}}/*** KNO工厂类*/static class KnoFactory implements Provider{@Overridepublic Function produce() {return new KnivesOut();}}/*** 测试* @param args*/public static void main(String[] args) {LolFactory lolFactory = new LolFactory();lolFactory.produce().play();KnoFactory knoFactory = new KnoFactory();knoFactory.produce().play();}
}

抽象工厂模式适合应用场景

         如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂

 抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。

         如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。

 在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

 实现方式

  1. 以不同的产品类型与产品变体为维度绘制矩阵。

  2. 为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。

  3. 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。

  4. 为每种产品变体实现一个具体工厂类。

  5. 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。

  6. 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。

 抽象工厂模式优缺点

优点:

  •  你可以确保同一工厂生成的产品相互匹配。
  •  你可以避免客户端和具体产品代码的耦合。
  •  单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  •  开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。

缺点:

  •  由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。
 与其他模式的关系
  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。

  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

  • 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。

  • 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

4 建造者模式

将一个复杂的对象的构建与他的表示进行分离,使用同样的构建过程但可以创建不同的表示。

/**
* 建造者模式
* <p>
*     建造者模式是将各种产品集中起来进行管理,用来创建复合对象。
* </p>
*/
public class BuilderDemo {static class Color{}static class Context{}protected static class DialogController{Color mColor;Context mContext;}/*** 目标类*/static class Dialog{private Context mContext;private Color mColor;protected Dialog(Context context) {mContext = context;}public void setColor(Color color) {mColor = color;}public void show() {System.out.println("Dialog show!");}/*** 建造者类*/static class Builder{private DialogController mDialogController = null;public Builder(Context context) {mDialogController = new DialogController();mDialogController.mContext = context;}public Builder setColor(Color color) {mDialogController.mColor = color;return this;}public Dialog build() {Dialog dialog = new Dialog(mDialogController.mContext);if (mDialogController.mColor != null) {dialog.setColor(mDialogController.mColor);}return dialog;}}}/*** 测试* @param args*/public static void main(String[] args) {Dialog dialog = new Dialog.Builder(new Context()).setColor(new Color()).build();dialog.show();}
}

建造者模式适合应用场景

         使用建造者模式可避免 “重叠构造函数 (telescoping constructor)” 的出现。

 假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。

class Pizza {Pizza(int size) { …… }Pizza(int size, boolean cheese) { …… }Pizza(int size, boolean cheese, boolean pepperoni) { …… }// ……

只有在 C# 或 Java 等支持方法重载的编程语言中才能写出如此复杂的构造函数。

建造者模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了。

         当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用建造者模式

 如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用建造者模式。

基本生成器接口中定义了所有可能的制造步骤, 具体生成器将实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序。

         使用生成器构造组合树或其他复杂对象。

建造者模式让你能分步骤构造产品。 你可以延迟执行某些步骤而不会影响最终产品。 你甚至可以递归调用这些步骤, 这在创建对象树时非常方便。

生成器在执行制造步骤时, 不能对外发布未完成的产品。 这可以避免客户端代码获取到不完整结果对象的情况。

实现方法

  1. 清晰地定义通用步骤, 确保它们可以制造所有形式的产品。 否则你将无法进一步实施该模式。

  2. 在基本生成器接口中声明这些步骤。

  3. 为每个形式的产品创建具体生成器类, 并实现其构造步骤。

    不要忘记实现获取构造结果对象的方法。 你不能在生成器接口中声明该方法, 因为不同生成器构造的产品可能没有公共接口, 因此你就不知道该方法返回的对象类型。 但是, 如果所有产品都位于单一类层次中, 你就可以安全地在基本接口中添加获取生成对象的方法。

  4. 考虑创建主管类。 它可以使用同一生成器对象来封装多种构造产品的方式。

  5. 客户端代码会同时创建生成器和主管对象。 构造开始前, 客户端必须将生成器对象传递给主管对象。 通常情况下, 客户端只需调用主管类构造函数一次即可。 主管类使用生成器对象完成后续所有制造任务。 还有另一种方式, 那就是客户端可以将生成器对象直接传递给主管类的制造方法。

  6. 只有在所有产品都遵循相同接口的情况下, 构造结果可以直接通过主管类获取。 否则, 客户端应当通过生成器获取构造结果。

 建造者模式优缺点

  •  你可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
  •  生成不同形式的产品时, 你可以复用相同的制造代码。
  •  单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。

  •  由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加。
 与其他模式的关系
  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。

  • 你可以在创建复杂组合模式树时使用生成器, 因为这可使其构造步骤以递归的方式运行。

  • 你可以结合使用生成器和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。

  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

5 原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是从一个对象出发得到一个和自己有相同状态的新对象的成熟模式,该模式的关键是将一个对象定义为原型,并为其提供复制自己的方法。

import java.io.*;/**
* 原型模式
* <p>
*     原型模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
* </p>
*/
public class Prototype implements Cloneable, Serializable {class SerializableObject implements Serializable {private static final long serialVersionUID = 1L;}private static final long serialVersionUID = 1L;private String string;private SerializableObject obj;/* 浅复制 */public Object clone() throws CloneNotSupportedException {return (Prototype) super.clone();}/* 深复制 */public Object deepClone() throws IOException, ClassNotFoundException {/* 写入当前对象的二进制流 */ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);/* 读出二进制流产生的新对象 */ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return ois.readObject();}public String getString() {return string;}public void setString(String string) {this.string = string;}public SerializableObject getObj() {return obj;}public void setObj(SerializableObject obj) {this.obj = obj;}
}

原型模式适合应用场景

         如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。

 这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。 即使不考虑代码耦合的情况, 你的代码也不能依赖这些对象所属的具体类, 因为你不知道它们的具体信息。

原型模式为客户端代码提供一个通用接口, 客户端代码可通过这一接口与所有实现了克隆的对象进行交互, 它也使得客户端代码与其所克隆的对象具体类独立开来。

 如果子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。 别人创建这些子类的目的可能是为了创建特定类型的对象。

 在原型模式中, 你可以使用一系列预生成的、 各种类型的对象作为原型。

客户端不必根据需求对子类进行实例化, 只需找到合适的原型并对其进行克隆即可。

 实现方式

  1. 创建原型接口, 并在其中声明 克隆方法。 如果你已有类层次结构, 则只需在其所有类中添加该方法即可。

  2. 原型类必须另行定义一个以该类对象为参数的构造函数。 构造函数必须复制参数对象中的所有成员变量值到新建实体中。 如果你需要修改子类, 则必须调用父类构造函数, 让父类复制其私有成员变量值。

    如果编程语言不支持方法重载, 那么你可能需要定义一个特殊方法来复制对象数据。 在构造函数中进行此类处理比较方便, 因为它在调用 new运算符后会马上返回结果对象。

  3. 克隆方法通常只有一行代码: 使用 new运算符调用原型版本的构造函数。 注意, 每个类都必须显式重写克隆方法并使用自身类名调用 new运算符。 否则, 克隆方法可能会生成父类的对象。

  4. 你还可以创建一个中心化原型注册表, 用于存储常用原型。

    你可以新建一个工厂类来实现注册表, 或者在原型基类中添加一个获取原型的静态方法。 该方法必须能够根据客户端代码设定的条件进行搜索。 搜索条件可以是简单的字符串, 或者是一组复杂的搜索参数。 找到合适的原型后, 注册表应对原型进行克隆, 并将复制生成的对象返回给客户端。

    最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。

 原型模式优缺点

  •  你可以克隆对象, 而无需与它们所属的具体类相耦合。
  •  你可以克隆预生成原型, 避免反复运行初始化代码。
  •  你可以更方便地生成复杂对象。
  •  你可以用继承以外的方式来处理复杂对象的不同配置。

  •  克隆包含循环引用的复杂对象可能会非常麻烦。
 与其他模式的关系
  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

  • 原型可用于保存命令模式的历史记录。

  • 大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

  • 原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 工厂方法基于继承, 但是它不需要初始化步骤。

  • 有时候原型可以作为备忘录模式的一个简化版本, 其条件是你需要在历史记录中存储的对象的状态比较简单, 不需要链接其他外部资源, 或者链接可以方便地重建。

  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

三、结构型模式

结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。

结构型模式:把类或对象结合在一起形成一个更大的结构。

6 代理模式

为其他对象提供一种代理以控制对这个对象的访问。

代理模式是为对象提供一个代理,代理可以控制对它所代理的对象的访问。

代理模式最常见的两种情况:远程代理和虚拟代理。

结构

  • 抽象主题(Subject) 该接口是对象和它的代理共同的接口。

  • 实际主题(RealSubject) 实际主题是实现抽象主题接口的类,是代理角色实例所要代理的对象。

  • 代理(Proxy) 是实现抽象主题接口的类,可以控制对他所包含的角色的实例的访问。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;/**
* 代理模式
* <p>
*     代理模式与装饰模式类似,都持有待操作类的对象,但装饰模式一般在编译时不明确待装饰对象,并且一般是增强待装饰对象的功能;而代理模式一般是在编译时就明确了
*     编译对象(内部创建待代理对象),主要用于限制访问,不暴露真实对象。
* </p>
*/
public class Proxy {interface InputStream{void read();}interface OutputStream{void write(byte b);}static class FileInputStream implements InputStream{@Overridepublic void read() {System.out.println("File read");}}static class FileOutputStream implements OutputStream{@Overridepublic void write(byte b) {System.out.println("File write");}}/*** 静态代理模式*/static class FileProxy implements InputStream, OutputStream{private InputStream mInputStream;private OutputStream mOutputStream;public FileProxy() {mInputStream = new FileInputStream();mOutputStream = new FileOutputStream();}@Overridepublic void read() {System.out.println("限制条件");mInputStream.read();}@Overridepublic void write(byte b) {System.out.println("限制条件");mOutputStream.write(b);}}/*** 动态代理模式(Java自带动态代理机制)*/static class DynamicProxy implements InvocationHandler{private OutputStream mOutputStream;public DynamicProxy(OutputStream outputStream) {mOutputStream = outputStream;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return method.invoke(mOutputStream, args);}}/*** 测试* @param args*/public static void main(String[] args) {FileProxy proxy = new FileProxy();proxy.read();proxy.write((byte) 0x01);DynamicProxy dynamicProxy = new DynamicProxy(new FileOutputStream());OutputStream outputStream = (OutputStream) java.lang.reflect.Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), newClass[]{OutputStream.class}, dynamicProxy);outputStream.write((byte) 0x02);}
}

代理模式适合应用场景

使用代理模式的方式多种多样, 我们来看看最常见的几种。

         延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。

 你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。

 代理可仅在客户端凭据满足要求时将请求传递给服务对象。

         本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。

 在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。

         记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。

 代理可以在向服务传递请求前进行记录。

         缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。

 代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。

         智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。

 代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。

 实现方式

  1. 如果没有现成的服务接口, 你就需要创建一个接口来实现代理和服务对象的可交换性。 从服务类中抽取接口并非总是可行的, 因为你需要对服务的所有客户端进行修改, 让它们使用接口。 备选计划是将代理作为服务类的子类, 这样代理就能继承服务的所有接口了。

  2. 创建代理类, 其中必须包含一个存储指向服务的引用的成员变量。 通常情况下, 代理负责创建服务并对其整个生命周期进行管理。 在一些特殊情况下, 客户端会通过构造函数将服务传递给代理。

  3. 根据需求实现代理方法。 在大部分情况下, 代理在完成一些任务后应将工作委派给服务对象。

  4. 可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。 你可以在代理类中创建一个简单的静态方法, 也可以创建一个完整的工厂方法。

  5. 可以考虑为服务对象实现延迟初始化。

 代理模式优缺点

  •  你可以在客户端毫无察觉的情况下控制服务对象。
  •  如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。
  •  即使服务对象还未准备好或不存在, 代理也可以正常工作。
  •  开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。

  •  代码可能会变得复杂, 因为需要新建许多类。
  •  服务响应可能会延迟。
 与其他模式的关系
  • 适配器模式能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。

  • 外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。

  • 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

7 适配器模式

适配器模式就是将一个类的接口转换成客户希望的另一个接口的过程,能够使得原来不兼容的对象一起工作

结构如下

  • 目标Target: 目标是一个接口,该接口是客户想要的接口

  • 被适配者Adaptee: 被适配者是一个已经存在的接口或者抽象类,需要进行适配

  • 适配器Adapter: 适配器是一个类,职责是对适配者的接口进行适配

/**
* 适配器模式
* <p>
*     适配器模式主要分为三种:
*     1.类的适配器模式{@link AdapterClass}:有一个{@link Source}类,拥有一个方法,待适配,目标接口是{@link Target},通过{@link AdapterClass}类,
*     将Source的功能扩展到Target里。
*     2.对象的适配器模式{@link AdapterObject}:适配器类AdapterObject持有待适配类Source的引用,实现适配目标接口Target,调用待适配类Source的方法,
*     可以解决兼容性问题(方法不一致性)。
*     3.接口的适配器模式{@link AdapterInterface}:在实际开发中,接口{@link DownloadListener}中定义了太多的方法,我们在一些实现类中并不是都需要,
*     则可以适配该接口。
* </p>
*/
public class Adapter {/*** 待适配类*/static class Source{public void method1() {System.out.println("Source method1");}}/*** 适配目标接口*/interface Target{void method1();void method2();}/*** 适配器类(类的适配器模式)*/static class AdapterClass extends Source implements Target{@Overridepublic void method2() {System.out.println("Target method2");}}/*** 适配器类(对象的适配器模式)*/static class AdapterObject implements Target{private Source mSource;public AdapterObject(Source source) {mSource = source;}@Overridepublic void method1() {mSource.method1();}@Overridepublic void method2() {System.out.println("Target method2");}}/*** 待适配接口(接口的适配器模式)*/interface DownloadListener{void onPre();void onStart();void onProgress(int progress);void onSuccess();void onError();void onCompleted();}/*** 抽象类空实现接口(接口的适配器模式)*/static abstract class SimpleDownloadListener implements DownloadListener{@Overridepublic void onPre() {}@Overridepublic void onStart() {}@Overridepublic void onProgress(int progress) {}@Overridepublic void onSuccess() {}@Overridepublic void onError() {}@Overridepublic void onCompleted() {}}/*** 适配接口,实现所需方法(接口的适配器模式)*/static class AdapterInterface extends SimpleDownloadListener{@Overridepublic void onProgress(int progress) {System.out.println("Progress: " + progress);}}
}

适配器模式适合应用场景

         当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。

 适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。

         如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。

 你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。

将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。

 实现方式

  1. 确保至少有两个类的接口不兼容:

    • 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
    • 一个或多个将受益于使用服务类的客户端类。
  2. 声明客户端接口, 描述客户端如何与服务交互。

  3. 创建遵循客户端接口的适配器类。 所有方法暂时都为空。

  4. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。

  5. 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。

  6. 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。

 适配器模式优缺点

  •  单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  •  开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

  •  代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
 与其他模式的关系
  • 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。

  • 适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。

  • 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。

  • 外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。

  • 桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

8 桥接模式

将抽象部分与它的实现部分分离,使得它们都可以独立地变化。

桥接模式是关于怎样将抽象部分与它的实现部分分离,使得它们都可以独立地变化的成熟模式。

结构

  • 抽象(Abstraction) 是一个抽象类,含有Implementor声明的变量,即维护一个Implementor类型的对象

  • 实现者(Implementor) 是一个接口,该接口中的方法不一定与Abstraction中的方法一致。Implementor负责定义基本操作,Abstraction负责定义高级操作

  • 细化抽象(Refined Abstraction) 是抽象角色的一个子类,该子类在重写抽象角色的方法是,给出一些必要的操作以后,将委托所维护的Implementor类型调用相应的方法

  • 具体实现者(Concrete Implementor) 是扩展Implementor接口的类

/**
* 桥接模式
* <p>
*     桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,功能的调用基本不变。
* </p>
*/
public class Bridge {/*** 桥接资源接口*/interface Source{void method();}/*** 桥接资源的第一种实现*/static class FirstSource implements Source{@Overridepublic void method() {System.out.println("First");}}/*** 桥接资源的第二种实现*/static class SecondSource implements Source{@Overridepublic void method() {System.out.println("Second");}}/*** 桥接资源的桥*/static abstract class BridgeSource{private Source mSource;protected Source getSource() {return mSource;}public void setSource(Source source) {mSource = source;}public abstract void method();}/*** 桥的管理类*/static class BridgeManage extends BridgeSource{@Overridepublic void method() {getSource().method();}}/*** 测试 客户启用功能* @param args*/public static void main(String[] args) {BridgeManage manage = new BridgeManage();Source source1 = new FirstSource();manage.setSource(source1);manage.method();Source source2 = new SecondSource();manage.setSource(source2);manage.method();}
}

桥接模式适合应用场景

         如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式

 类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。

桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。

         如果你希望在几个独立维度上扩展一个类, 可使用该模式。

 桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。

         如果你需要在运行时切换不同实现方法, 可使用桥接模式。

 当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。

顺便提一句, 最后一点是很多人混淆桥接模式和策略模式的主要原因。 记住, 设计模式并不仅是一种对类进行组织的方式, 它还能用于沟通意图和解决问题。

 实现方式

  1. 明确类中独立的维度。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。

  2. 了解客户端的业务需求, 并在抽象基类中定义它们。

  3. 确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。

  4. 为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。

  5. 在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。

  6. 如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。

  7. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。

 桥接模式优缺点

  •  你可以创建与平台无关的类和程序。
  •  客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
  •  开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
  •  单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。

  •  对高内聚的类使用该模式可能会让代码更加复杂。
 与其他模式的关系
  • 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。

  • 桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 你可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

  • 你可以结合使用生成器模式和桥接模式: 主管类负责抽象工作, 各种不同的生成器负责实现工作。

9 装饰模式

装饰模式是动态的扩展一个类的功能,而不需要改变原始类的代码。

结构

主要包含四个角色:

  • 抽象组建:抽象组建是一个抽象类,定义了被装饰者需要装饰的方法

  • 具体组建:是抽象组建的一个子类,具体组建的实例成为“被装饰者”

  • 装饰Decorator: 装饰者可以是一个抽象类也可以是一个非抽象类,是抽象组建的一个子类,但是还包含抽象组建的一个变量以保存“被装饰者”的引用。

  • 具体装饰:是一个非抽象的子类,具体装饰的实例被称为“装饰者”

/**
* 装饰模式
* <p>
*     装饰模式可以扩展一个类的功能,动态的为一个对象增加功能,还能动态的撤销。
* </p>
*/
public class Decorator {/*** 装饰接口*/interface Source{void method();}/*** 待装饰实现类*/static class SourceImpl implements Source{@Overridepublic void method() {System.out.println("Source something");}}/*** 装饰类*/static class DecoratorSource implements Source{private Source mSource;public DecoratorSource(Source source) {mSource = source;}@Overridepublic void method() {System.out.println("Decorator before source");mSource.method();System.out.println("Decorator after source");}}/*** 测试* @param args*/public static void main(String[] args) {Source source = new DecoratorSource(new SourceImpl());source.method();}
}

 装饰模式适合应用场景

         如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。

 装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。

         如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式

 许多编程语言使用 final最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。

 实现方式

  1. 确保业务逻辑可用一个基本组件及多个额外可选层次表示。

  2. 找出基本组件和可选层次的通用方法。 创建一个组件接口并在其中声明这些方法。

  3. 创建一个具体组件类, 并定义其基础行为。

  4. 创建装饰基类, 使用一个成员变量存储指向被封装对象的引用。 该成员变量必须被声明为组件接口类型, 从而能在运行时连接具体组件和装饰。 装饰基类必须将所有工作委派给被封装的对象。

  5. 确保所有类实现组件接口。

  6. 将装饰基类扩展为具体装饰。 具体装饰必须在调用父类方法 (总是委派给被封装对象) 之前或之后执行自身的行为。

  7. 客户端代码负责创建装饰并将其组合成客户端所需的形式。

 装饰模式优缺点

  •  你无需创建新子类即可扩展对象的行为。
  •  你可以在运行时添加或删除对象的功能。
  •  你可以用多个装饰封装对象来组合几种行为。
  •  单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。

  •  在封装器栈中删除特定封装器比较困难。
  •  实现行为不受装饰栈顺序影响的装饰比较困难。
  •  各层的初始化配置代码看上去可能会很糟糕。
 与其他模式的关系
  • 适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。

  • 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。

  • 责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。

    责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

  • 组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

    装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

    但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。

  • 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

  • 装饰可让你更改对象的外表, 策略模式则让你能够改变其本质。

  • 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

10 外观模式

为系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得子系统更加容易使用

结构

  • 子系统:子系统是由若干个类组成的集合,这些类的实例协同合作为用户提供所需要的功能,子系统中的任何类都不包含外观类的实例引用

  • 外观:外观是一个类,该类包含子系统中全部或者部分类的实例引用,当用户需要和子系统的实例打交道的时候,可以代替得和子系统的外观实例打交道

/**
* 外观模式
* <p>
*     外观模式是为了解决类与类之间的依赖关系,像spring一样,可以将类和类之间的关系配置到配置文件中,
*     而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口。
* </p>
*/
public class Facade {static class First{String start() {System.out.println("Start game");return "ready";}}static class Second{void ready(String str){System.out.println("Init has completed:" + str);}}static class Third{void play() {System.out.println("Come on!");}}/*** 外观包装类*/static class FacadeClass{private First mFirst;private Second mSecond;private Third mThird;public FacadeClass() {mFirst = new First();mSecond = new Second();mThird = new Third();}public void playGame() {String start = mFirst.start();mSecond.ready(start);mThird.play();}}/*** 测试 客户使用* @param args*/public static void main(String[] args) {FacadeClass facadeClass = new FacadeClass();facadeClass.playGame();}
}

外观模式适合应用场景

         如果你需要一个指向复杂子系统的直接接口, 且该接口的功能有限, 则可以使用外观模式。

 子系统通常会随着时间的推进变得越来越复杂。 即便是应用了设计模式, 通常你也会创建更多的类。 尽管在多种情形中子系统可能是更灵活或易于复用的, 但其所需的配置和样板代码数量将会增长得更快。 为了解决这个问题, 外观将会提供指向子系统中最常用功能的快捷方式, 能够满足客户端的大部分需求。

         如果需要将子系统组织为多层结构, 可以使用外观。

 创建外观来定义子系统中各层次的入口。 你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。

让我们回到视频转换框架的例子。 该框架可以拆分为两个层次: 音频相关和视频相关。 你可以为每个层次创建一个外观, 然后要求各层的类必须通过这些外观进行交互。 这种方式看上去与中介者模式非常相似。

 实现方式

  1. 考虑能否在现有子系统的基础上提供一个更简单的接口。 如果该接口能让客户端代码独立于众多子系统类, 那么你的方向就是正确的。

  2. 在一个新的外观类中声明并实现该接口。 外观应将客户端代码的调用重定向到子系统中的相应对象处。 如果客户端代码没有对子系统进行初始化, 也没有对其后续生命周期进行管理, 那么外观必须完成此类工作。

  3. 如果要充分发挥这一模式的优势, 你必须确保所有客户端代码仅通过外观来与子系统进行交互。 此后客户端代码将不会受到任何由子系统代码修改而造成的影响, 比如子系统升级后, 你只需修改外观中的代码即可。

  4. 如果外观变得过于臃肿, 你可以考虑将其部分行为抽取为一个新的专用外观类。

 外观模式优缺点

  •  你可以让自己的代码独立于复杂子系统。

  •  外观可能成为与程序中所有类都耦合的上帝对象。
 与其他模式的关系
  • 外观模式为现有对象定义了一个新接口, 适配器模式则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。

  • 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂模式来代替外观。

  • 享元模式展示了如何生成大量的小型对象, 外观则展示了如何用一个对象来代表整个子系统。

  • 外观和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。

    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 外观类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。

  • 外观与代理模式的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。

11 组合模式

将对象组合成数形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

组合模式是关于怎样将对象形成树形结构来表现整体和部分的层次结构的成熟模式。使用组合模式,可以让用户以一致的方式处理个体对象和组合对象。

组合模式的关键在于无论是个体对象还是组合对象都实现了相同的接口或都是同一个抽象类的子类。

结构

抽象组件Component: 定义了个体对象和组合对象需要实现的关于操作其子节点的方法 Composite节点Composite Node: 实现了Component接口,也可以包含其他Composite节点或者Leaf节点的引用 Leaf节点Leaf Node: 实现Composite接口类的实例

/**
* 组合模式
* <p>
*     组合模式有时又叫 部分-整体 模式,在处理类似树形结构的问题时比较方便。
* </p>
*/
public class Composite {/*** 外部整体类* @param <T>*/static class Tree<T>{private TreeNode<T> root;private TreeNode<T> point;private int size;public void add(T data) {TreeNode<T> node = new TreeNode<>(data);if (root == null) {root = node;point = node;}else {point.add(node);point = node;}size++;}public boolean remove(T data) {TreeNode<T> temp = root;while (temp != null) {if (temp.data == data) {if (temp.equals(root)) {root = temp.next;}temp.remove();size--;return true;}temp = temp.next;}return false;}public int size() {return size;}public void print() {TreeNode<T> temp = root;while (temp != null) {System.out.println(temp.data);temp = temp.next;}}/*** 内部节点类* @param <T>*/static class TreeNode<T>{private T data;private TreeNode<T> pre;private TreeNode<T> next;private TreeNode(T data) {this.data = data;}private void add(TreeNode node) {next = node;node.pre = this;}private void remove() {if (pre != null) {pre.next = next;}if (next != null) {next.pre = pre;}pre = null;next = null;data = null;}}}/*** 测试* @param args*/public static void main(String[] args) {Tree<String> tree = new Tree<>();boolean a = tree.remove("a");System.out.println(a);System.out.println(tree.size());tree.add("a");tree.add("b");tree.add("c");tree.add("d");tree.print();System.out.println(tree.size());tree.remove("a");tree.remove("c");tree.print();System.out.println(tree.size());}
}

组合模式适合应用场景

         如果你需要实现树状对象结构, 可以使用组合模式。

 组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。

         如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。

 组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。

 实现方式

  1. 确保应用的核心模型能够以树状结构表示。 尝试将其分解为简单元素和容器。 记住, 容器必须能够同时包含简单元素和其他容器。

  2. 声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。

  3. 创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。

  4. 创建一个容器类表示复杂元素。 在该类中, 创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此请确保将其声明为组合接口类型。

    实现组件接口方法时, 记住容器应该将大部分工作交给其子元素来完成。

  5. 最后, 在容器中定义添加和删除子元素的方法。

    记住, 这些操作可在组件接口中声明。 这将会违反接口隔离原则, 因为叶节点类中的这些方法为空。 但是, 这可以让客户端无差别地访问所有元素, 即使是组成树状结构的元素。

 组合模式优缺点

  •  你可以利用多态和递归机制更方便地使用复杂树结构。
  •  开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。

  •  对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。
 与其他模式的关系
  • 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。

  • 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 你可以使用迭代器模式来遍历组合树。

  • 你可以使用访问者模式对整个组合树执行操作。

  • 你可以使用享元模式实现组合树的共享叶节点以节省内存。

  • 组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。

    装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。

    但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。

  • 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。

12 享元模式

运用共享技术有效地支持大量细粒度的对象。

一个类中的成员变量表明该类所创建的对象所具有的属性,在某些程序设计中我们可能用一个类创建若干个对象,但是我们发现这些对象的一个共同特点是它们有一部分属性的取值必须是完全相同的。

结构

  • 享元接口(Flyweight):定义了享元对外公开数据的方法和接收外部数据的方法

  • 具体享元(Concrete Flyweight): 保证使用享元对象的应用程序无法修改享元内部的数据,因为要保证享元对象是共享的,创建和管理享元对象必须由享元工厂负责

  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,可以采用单例模式进行设计

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Vector;/**
* 享元模式
* <p>
*     享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
* </p>
*/
public class Flyweight {/*** 连接池*/static class ConnectionPool{private Vector<Connection> mPool;private String url = "jdbc:mysql://localhost:3306/test";private String driverClassName = "com.mysql.jdbc.Driver";private String account = "root";private String password = "123456";private int poolSize = 50;private Connection con;public ConnectionPool() {mPool = new Vector<>();for (int i = 0; i < poolSize; i++) {try {Class.forName(driverClassName);Connection connection = DriverManager.getConnection(url, account, password);mPool.add(connection);} catch (Exception e) {e.printStackTrace();}}}public synchronized void release() {mPool.add(con);con = null;}public synchronized Connection getConnection() {if (mPool.size() > 0) {con = mPool.get(0);mPool.remove(con);return con;}return null;}}/*** 测试* @param args*/public static void main(String[] args) {ConnectionPool pool = new ConnectionPool();Connection connection = pool.getConnection();System.out.println(connection);}
}

享元模式适合应用场景

 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。

 应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

  • 程序需要生成数量巨大的相似对象
  • 这将耗尽目标设备的所有内存
  • 对象中包含可抽取且能在多个对象间共享的重复状态。

 实现方式

  1. 将需要改写为享元的类成员变量拆分为两个部分:

    • 内在状态: 包含不变的、 可在许多对象中重复使用的数据的成员变量。
    • 外在状态: 包含每个对象各自不同的情景数据的成员变量
  2. 保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。 这些变量仅可在构造函数中获得初始数值。

  3. 找到所有使用外在状态成员变量的方法, 为在方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量。

  4. 你可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂。

  5. 客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。

 享元模式优缺点

  •  如果程序中有很多相似对象, 那么你将可以节省大量内存。
  •  你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。
  •  代码会变得更加复杂。 团队中的新成员总是会问: ​ “为什么要像这样拆分一个实体的状态?”。
 与其他模式的关系
  • 你可以使用享元模式实现组合模式树的共享叶节点以节省内存。

  • 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统。

  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同。

    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。

四、行为模式

行为模式负责对象间的高效沟通和职责委派。

行为型模式:类和对象如何交互,及划分责任和算法。

13 策略模式

定义一系列算法,把他们封装起来,并且他们可以互相替换,可以独立于使用它的客户端而变换。该模式的核心就是将经常需要变化的类抽离出来,将每一种可能的变化对应的交给抽象类或接口的子类去实现。

基本概念

策略:封装算法标识的接口是策略 具体策略:实现接口的策略是具体策略 上下文:依赖于策略接口的类

/**
* 策略模式
* <p>
*     策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。
*     策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。
*     策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可
* </p>
*/
public class Strategy {/*** 计算接口*/interface ICalculator{int calculate(String expression);}/*** 辅助计算类*/static abstract class Assist{protected int[] split(String exp, String regex) {String[] split = exp.split(regex);int[] ints = new int[2];ints[0] = Integer.parseInt(split[0]);ints[1] = Integer.parseInt(split[1]);return ints;}}/*** 加法运算*/static class Plus extends Assist implements ICalculator{@Overridepublic int calculate(String expression) {int[] ints = split(expression, "\\+");return ints[0] + ints[1];}}/*** 减法运算*/static class Minus extends Assist implements ICalculator{@Overridepublic int calculate(String expression) {int[] ints = split(expression, "-");return ints[0] - ints[1];}}/*** 乘法运算*/static class Multiply extends Assist implements ICalculator{@Overridepublic int calculate(String expression) {int[] ints = split(expression, "\\*");return ints[0] * ints[1];}}/*** 除法运算*/static class Division extends Assist implements ICalculator{@Overridepublic int calculate(String expression) {int[] ints = split(expression, "\\/");return ints[0] / ints[1];}}/*** 测试* @param args*/public static void main(String[] args) {System.out.println(new Plus().calculate("10+20"));System.out.println(new Minus().calculate("100-55"));System.out.println(new Multiply().calculate("11*22"));System.out.println(new Division().calculate("999/67"));}
}

 策略模式适合应用场景

 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。

 策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。

 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。

 策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。

 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。

 策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。

 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。

 策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。

 实现方式

  1. 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符)。

  2. 声明该算法所有变体的通用策略接口。

  3. 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。

  4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。

  5. 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。

 策略模式优缺点

  •  你可以在运行时切换对象内的算法。
  •  你可以将算法的实现和使用算法的代码隔离开来。
  •  你可以使用组合来代替继承。
  •  开闭原则。 你无需对上下文进行修改就能够引入新的策略。
  •  如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
  •  客户端必须知晓策略间的不同——它需要选择合适的策略。
  •  许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。
 与其他模式的关系
  • 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 命令模式和策略看上去很像, 因为两者都能通过某些行为来参数化对象。 但是, 它们的意图有非常大的不同。

    • 你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。

    • 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。

  • 装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。

  • 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。

  • 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。

14 观察者模式

官方解释:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖的对象能够得到通知并自动更新。

观察者模式是关于一个对象想知道另一个对象中数据变化情况的一种成熟的模式。观察者模式中存在“主题”和若干个“观察者”,把“主题”中变化的东西抽离出来,当发生变化时,所有的“观察者”能够得到通知。

应用场景

很多消息中间件是采用这种观察者模式,Java并发编程中异步回调的方式也类似于观察者模式。

角色分析

  1. 主题:主题是一个接口,该接口规定了具体主题需要实现的方法;

  2. 观察者:观察者是一个接口,该接口规定了具体观察者用来更新数据的方法;

  3. 具体主题:具体主题是实现主题接口的一个实例;

  4. 具体观察者:是实现观察者接口的一个实例;

import javax.security.auth.Subject;
import java.util.Vector;/**
* 观察者模式
* <p>
*     观察者模式即监听模式,描述类与类之间的关系,主要是在目标类中注册监听器,当目标类的有效值实时改变时,通过监听器及时回调通知各个注册者。
* </p>
*/
public class Observer {/*** 观察者接口*/interface IObserver{void update(String data);}/*** 注册接口*/interface Subject{/*** 增加观察者* @param observer*/void add(IObserver observer);/*** 删除观察者* @param observer*/void del(IObserver observer);/*** 通知更新*/void notifyObservers();/*** 自身操作*/void operation();}/*** 抽象注册接口*/static abstract class AbstractSubject implements Subject {private Vector<IObserver> mObservers = new Vector<>();@Overridepublic void add(IObserver observer) {mObservers.add(observer);}@Overridepublic void del(IObserver observer) {mObservers.remove(observer);}@Overridepublic void notifyObservers() {for (IObserver observer : mObservers) {observer.update("Hello World");}}}/*** 注册实现*/static class SubjectImpl extends AbstractSubject{@Overridepublic void operation() {notifyObservers();}}/*** 测试* @param args*/public static void main(String[] args) throws InterruptedException {SubjectImpl subject = new SubjectImpl();subject.add(System.out::println);subject.add(System.out::println);Thread.sleep(1000);subject.operation();}
}

观察者模式适合应用场景

         当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。

 当你使用图形用户界面类时通常会遇到一个问题。 比如, 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码, 这样当用户按下按钮时就会触发这些代码。

观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。 你可在按钮中添加订阅机制, 允许客户端通过自定义订阅类注入自定义代码。

         当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。

 订阅列表是动态的, 因此订阅者可随时加入或离开该列表。

 实现方式

  1. 仔细检查你的业务逻辑, 试着将其拆分为两个部分: 独立于其他代码的核心功能将作为发布者; 其他代码则将转化为一组订阅类。

  2. 声明订阅者接口。 该接口至少应声明一个 update方法。

  3. 声明发布者接口并定义一些接口来在列表中添加和删除订阅对象。 记住发布者必须仅通过订阅者接口与它们进行交互。

  4. 确定存放实际订阅列表的位置并实现订阅方法。 通常所有类型的发布者代码看上去都一样, 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。 具体发布者会扩展该类从而继承所有的订阅行为。

    但是, 如果你需要在现有的类层次结构中应用该模式, 则可以考虑使用组合的方式: 将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。

  5. 创建具体发布者类。 每次发布者发生了重要事件时都必须通知所有的订阅者。

  6. 在具体订阅者类中实现通知更新的方法。 绝大部分订阅者需要一些与事件相关的上下文数据。 这些数据可作为通知方法的参数来传递。

    但还有另一种选择。 订阅者接收到通知后直接从通知中获取所有数据。 在这种情况下, 发布者必须通过更新方法将自身传递出去。 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。

  7. 客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。

 观察者模式优缺点

  •  开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。
  •  你可以在运行时建立对象之间的联系。

  •  订阅者的通知顺序是随机的。
 与其他模式的关系
  • 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。

    当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。

    假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

15 责任链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象形成一条链,并沿着这条链传递该请求,知道有一个对象处理它为止。

责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

结构

处理者Handler: 处理者是一个接口,负责规定具体处理者处理用户请求的方法以及具体处理者设置后继对象的方法

具体处理者ConcreteHandler:实现处理者接口的实例,处理者通过调用处理者接口的规定方法处理用户的请求,如果能处理则处理,否则反馈无法处理的信息给用户

/**
* 责任链模式
* <p>
*     责任链模式:有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。
*     但是发出者并不清楚到底最终哪个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。
* </p>
*/
public class ChainResponse {/*** 功能接口*/interface Chain{void operate();}/*** 加工类*/static class AbstractChain{private Chain mChain;public Chain getChain() {return mChain;}public void setChain(Chain chain) {mChain = chain;}}/*** 实现类*/static class ManageChain extends AbstractChain implements Chain{private String mName;public ManageChain(String name) {mName = name;}@Overridepublic void operate() {System.out.println("Hello "+mName);if (getChain() != null) {getChain().operate();}}}/*** 测试* @param args*/public static void main(String[] args) {ManageChain chain1 = new ManageChain("Chain1");ManageChain chain2 = new ManageChain("Chain2");ManageChain chain3 = new ManageChain("Chain3");chain1.setChain(chain2);chain2.setChain(chain3);chain1.operate();}
}

 责任链模式适合应用场景

 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式。

 该模式能将多个处理者连接成一条链。 接收到请求后, 它会 “询问” 每个处理者是否能够对其进行处理。 这样所有处理者都有机会来处理请求。

 当必须按顺序执行多个处理者时, 可以使用该模式。

 无论你以何种顺序将处理者连接成一条链, 所有请求都会严格按照顺序通过链上的处理者。

 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。

 如果在处理者类中有对引用成员变量的设定方法, 你将能动态地插入和移除处理者, 或者改变其顺序。

 实现方式

  1. 声明处理者接口并描述请求处理方法的签名。

    确定客户端如何将请求数据传递给方法。 最灵活的方式是将请求转换为对象, 然后将其以参数的形式传递给处理函数。

  2. 为了在具体处理者中消除重复的样本代码, 你可以根据处理者接口创建抽象处理者基类。

    该类需要有一个成员变量来存储指向链上下个处理者的引用。 你可以将其设置为不可变类。 但如果你打算在运行时对链进行改变, 则需要定义一个设定方法来修改引用成员变量的值。

    为了使用方便, 你还可以实现处理方法的默认行为。 如果还有剩余对象, 该方法会将请求传递给下个对象。 具体处理者还能够通过调用父对象的方法来使用这一行为。

  3. 依次创建具体处理者子类并实现其处理方法。 每个处理者在接收到请求后都必须做出两个决定:

    • 是否自行处理这个请求。
    • 是否将该请求沿着链进行传递。
  4. 客户端可以自行组装链, 或者从其他对象处获得预先组装好的链。 在后一种情况下, 你必须实现工厂类以根据配置或环境设置来创建链。

  5. 客户端可以触发链中的任意处理者, 而不仅仅是第一个。 请求将通过链进行传递, 直至某个处理者拒绝继续传递, 或者请求到达链尾。

  6. 由于链的动态性, 客户端需要准备好处理以下情况:

    • 链中可能只有单个链接。
    • 部分请求可能无法到达链尾。
    • 其他请求可能直到链尾都未被处理。

 责任链模式优缺点

  •  你可以控制请求处理的顺序。
  •  单一职责原则。 你可对发起操作和执行操作的类进行解耦。
  •  开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者。

  •  部分请求可能未被处理。
 与其他模式的关系
  • 责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 责任链通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。

  • 责任链的管理者可使用命令模式实现。 在这种情况下, 你可以对由请求代表的同一个上下文对象执行许多不同的操作。

    还有另外一种实现方式, 那就是请求自身就是一个命令对象。 在这种情况下, 你可以对由一系列不同上下文连接而成的链执行相同的操作。

  • 责任链和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。

    责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。

16 模板模式

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法是关于怎样将若干个方法集成到一个方法中,以便形成一个解决问题的算法骨架。模板方法模式的关键是在一个抽象类中定义一个算法的骨架,即将若干个方法集成到一个方法中,并称该方法为一个模板方法,或简称为模板。

结构

  • 抽象模板(Abstract Template)抽象模板是一个抽象类,定义了若干个抽象的方法,以表示一个算法的各个步骤,抽象方法成为原语操作。

  • 具体模板(Concrete Template)具体模板是抽象模板的子类,实现抽象模板中的原语操作

/**
* 模板方法模式
* <p>
*     模板方法模式主要是创建一个抽象类,其中包含模板方法(主方法),而类中其他方法均由主方法调配,子类可以根据自己的特性重写其他方法,从而达到需求。
* </p>
*/
public class TemplateMethod {/*** 模板类*/static abstract class AbstractCalculator {/*主方法,实现对本类其它方法的调用*/public final int calculate(String exp,String opt){int array[] = split(exp,opt);return calculate(array[0],array[1]);}/*被子类重写的方法*/abstract public int calculate(int num1,int num2);public int[] split(String exp,String opt){String array[] = exp.split(opt);int arrayInt[] = new int[2];arrayInt[0] = Integer.parseInt(array[0]);arrayInt[1] = Integer.parseInt(array[1]);return arrayInt;}}/*** 实现类 加法运算*/static class Plus extends AbstractCalculator {@Overridepublic int calculate(int num1,int num2) {return num1 + num2;}}/*** 测试* @param args*/public static void main(String[] args) {AbstractCalculator calculator = new Plus();int calculate = calculator.calculate("998+98", "\\+");System.out.println(calculate);}
}

17 状态模式

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

一个对象的状态依赖于它的变量的取值情况,对象在不同的运行环境中,可能具有不同的状态。在许多情况下,对象调用方法所产生的行为效果依赖于它当时的状态。

状态模式的关键是将对象的状态封装成为独立的类,对象调用方法时,可以委托当前对象所具有的状态调用相应的方法,使得当前对象看起来好像修改了它的类。

结构

  • 环境(Context) 环境是一个类,该类含有抽象状态声明的变量,可以引用任何具体状态类的实例,用户对该环境类的实例在某个状态下的行为感兴趣

  • 抽象状态(State) 抽象状态是一个接口或者抽象类,抽象状态中定义了与环境的一个特定状态相关的若干个方法

  • 具体状态(Concrete State)具体状态是实现抽象状态的类

/**
* 状态模式
* <p>
*     核心思想就是:当对象的状态改变时,同时改变其行为。
* </p>
*/
public class StateDemo {/*** 状态类*/static class State{private String value;public String getValue() {return value;}public void setValue(String value) {this.value = value;}public void method1() {System.out.println("state1 do something");}public void method2() {System.out.println("state2 do something");}}/*** 状态行为切换类*/static class Context{private State mState;public State getState() {return mState;}public void setState(State state) {mState = state;}public Context(State state) {mState = state;}public void method() {switch (mState.value) {case "state1":mState.method1();break;case "state2":mState.method2();break;}}}/*** 测试* @param args*/public static void main(String[] args) {State state = new State();Context context = new Context(state);state.setValue("state1");context.method();state.setValue("state2");context.method();}
}

18 访问者模式

表示一个作用于某对象结构中的各个元素的操作。它使你可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。

当一个集合中有若干个对象时,习惯上将这些对象称作集合中的元素,访问者模式可以使得我们在不改变集合中各个元素的类的前提下定义作用于这些元素上的新操作。

结构

  • 抽象元素(Element) :抽象类定义了接收访问者的accept操作

  • 具体元素(Concrete Element) :Element的子类

  • 对象结构(Object Structure) :一个集合,用于存放Element对象,提供了遍历它自己的方法

  • 抽象访问者(Visitor) :一个接口,定义了操作对象(Concrete Element) 的方法

  • 具体访问者(Concrete Visitor) 实现Visitor接口的类

/**
* 访问者模式
* <p>
*     访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。
*     因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。
*     访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。
*     访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。
* </p>
*/
public class VisitorDemo {/*** 访问者接口*/interface Visitor{void visit(Subject subject);}/*** 被访问者*/interface Subject{void accept(Visitor visitor);String getSubject();}/*** 访问者实现类*/static class VisitorImpl implements Visitor{@Overridepublic void visit(Subject subject) {System.out.println(subject.getSubject());}}/*** 被访问者实现类*/static class SubjectImpl implements Subject{private String value;public SubjectImpl(String value) {this.value = value;}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}@Overridepublic String getSubject() {return value;}}/*** 测试* @param args*/public static void main(String[] args) {Visitor visitor = new VisitorImpl();Subject subject = new SubjectImpl("Hello Visitor");subject.accept(visitor);}
}

19.备忘录模式

表示一个作用于某对象结构中的各个元素的操作。它使你可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。

备忘录模式是关于怎样保存对象状态的成熟模式,其关键是提供一个备忘录对象,该备忘录负责存储一个对象的状态,程序可以在磁盘或内存中保存这个备忘录,这样一来,程序就可以根据对象的备忘录将该对象恢复到备忘录中所存储的状态。

结构

  • 原发者(Originator) 需要在某个时刻保存其状态的对象。负责创建备忘录,然后使用该备忘录记录自己的状态

  • 备忘录(Memento) 负责存储原发者状态的对象

  • 负责人(Caretaker) 负责管理保存备忘录的对象,可以通过使用对象流将备忘录写入文件


/**
* 备忘录模式
* <p>
*     备忘录模式的主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象。
* </p>
*/
public class MementoDemo {/*** 备忘录类*/static class Memento{private String value;public String getValue() {return value;}public void setValue(String value) {this.value = value;}public Memento(String value) {this.value = value;}}/*** 原始类*/static class Original{private String value;public String getValue() {return value;}public void setValue(String value) {this.value = value;}public Original(String value) {this.value = value;}public Memento createMemento() {return new Memento(value);}public void restoreMemento(Memento memento) {this.value = memento.value;}}/*** 备忘录存储类*/static class Storage{private Memento mMemento;public Memento getMemento() {return mMemento;}public void setMemento(Memento memento) {mMemento = memento;}public Storage(Memento memento) {mMemento = memento;}}/*** 测试* @param args*/public static void main(String[] args) {Original original = new Original("你好");System.out.println("1."+original.getValue());Storage storage = new Storage(original.createMemento());original.setValue("不好");System.out.println("2."+original.getValue());original.restoreMemento(storage.getMemento());System.out.println("3."+original.getValue());}
}

20.中介者模式

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使耦合松散,而且可以独立的改变他们之间的交互

结构

  • 中介者Mediator:中介者是一个接口,用于定义同事对象之间通信的方法

  • 具体中介者ConcreteMediator: 需要包含所有具体同事的引用

  • 同事Colleague: 一个接口,规定了具体同事需要实现的方法

  • 具体同事ConcreteColleague: 实现同事接口的类,只需要将具体的请求通知给包含他的中介者即可

/**
* 中介者模式
* <p>
* 中介者模式是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。
* 如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。
* </p>
*/
public class MediatorDemo {/*** 中介者接口*/interface Mediator {void createMediator();void workAll();}/*** 用户抽象类*/static abstract class User {private Mediator mMediator;public Mediator getMediator() {return mMediator;}public User(Mediator mediator) {mMediator = mediator;}public abstract void work();}/*** 用户1*/static class User1 extends User {public User1(Mediator mediator) {super(mediator);}@Overridepublic void work() {System.out.println("User1 do something");}}/*** 用户2*/static class User2 extends User {public User2(Mediator mediator) {super(mediator);}@Overridepublic void work() {System.out.println("User2 do something");}}/*** 中介者实现类*/static class MediatorImpl implements Mediator {private User1 mUser1;private User2 mUser2;public User1 getUser1() {return mUser1;}public User2 getUser2() {return mUser2;}@Overridepublic void createMediator() {mUser1 = new User1(this);mUser2 = new User2(this);}@Overridepublic void workAll() {mUser1.work();mUser2.work();}}/*** 测试* @param args*/public static void main(String[] args) {Mediator mediator = new MediatorImpl();mediator.createMediator();mediator.workAll();}
}

21. 迭代器模式

提供一种方法可以顺序访问一个聚合对象中的各个元素,而又不需要暴露对象的内部表示

结构

  • 集合Aggregate:一个接口,规定了集合需要实现的操作

  • 具体集合ConcreteAggregate:实现集合接口的具体实例,按照一定的结构存储对象,具体集合应该有一个方法,返回针对该集合的迭代器

  • 迭代器Iterator:一个接口,规定了遍历具体集合的方法,比如next()方法

  • 具体迭代器ConcreteIterator: 实现迭代器接口的具体实例

/**
* 迭代器模式
* <p>
*     迭代器模式就是顺序访问聚集中的对象,这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。
* </p>
*/
public class IteratorDemo {/*** 迭代器接口*/interface Iterator{Object pre();Object next();boolean hasNext();Object first();}/*** 集合接口*/interface Collection{Iterator iterator();Object get(int i);int size();}/*** 迭代器实现类*/static class IteratorImpl implements Iterator{private Collection mCollection;private int pos = -1;public IteratorImpl(Collection collection) {mCollection = collection;}@Overridepublic Object pre() {if (pos > 0) {pos--;}return mCollection.get(pos);}@Overridepublic Object next() {if (pos < mCollection.size() - 1) {pos++;}return mCollection.get(pos);}@Overridepublic boolean hasNext() {return pos < mCollection.size() - 1;}@Overridepublic Object first() {pos = 0;return mCollection.get(pos);}}/*** 集合实现类*/static class CollectionImpl implements Collection{public String[] mStrings = {"a", "b", "c", "d", "e", "f", "g"};@Overridepublic Iterator iterator() {return new IteratorImpl(this);}@Overridepublic Object get(int i) {return mStrings[i];}@Overridepublic int size() {return mStrings.length;}}/*** 测试* @param args*/public static void main(String[] args) {Collection collection = new CollectionImpl();Iterator iterator = collection.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}
}

22 解析器模式

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

结构

  • 抽象表达式(AbstractExpression) 负责定义抽象的解释角色

  • 终结符表达式子(TerminalExpression) 实现AbstractExpression接口,该类将接口中的解释操作实现为与文法中的终结符相关联的操作

  • 非终结符表达式子(NonterminalExpression) 实现AbstractExpression的类,为文法的非终结符实现解释操作

  • 上下文(Context) 包含解释器之外的一些全局信息

/**
* 解释器模式
* <p>
*     解释器模式一般主要应用在OOP开发中的编译器的开发中。
* </p>
*/
public class Interpreter {/*** 上下文*/static class Context{private int num1;private int num2;public int getNum1() {return num1;}public void setNum1(int num1) {this.num1 = num1;}public int getNum2() {return num2;}public void setNum2(int num2) {this.num2 = num2;}public Context(int num1, int num2) {this.num1 = num1;this.num2 = num2;}}/*** 表达式接口*/interface Expression{int interpret(Context context);}/*** 加法解释器*/static class Plus implements Expression{@Overridepublic int interpret(Context context) {return context.getNum1() + context.getNum2();}}/*** 减法解释器*/static class Minus implements Expression{@Overridepublic int interpret(Context context) {return context.getNum1() - context.getNum2();}}/*** 测试* @param args*/public static void main(String[] args) {//998-668+1080int interpret = new Plus().interpret(new Context(new Minus().interpret(new Context(998, 668)), 1080));System.out.println(interpret);}
}

23 命令模式

将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。

结构

主要包含四个角色

  • 接收者Receiver:接收者是一个类的实例,负责执行与请求相关的操作

  • 命令接口Command:命令是一个接口,用于封装请求的若干个方法

  • 具体命令ConcreteCommand:实现接口类的实例

  • 请求者Invoker:负责调用具体命令

/**
* 命令模式
* <p>
*     命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开。
* </p>
*/
public class CommandDemo {/*** 命令接口*/interface Command{void exe();}/*** 命令发出者*/static class Invoker{private Command mCommand;public Invoker(Command command) {mCommand = command;}public void action() {mCommand.exe();}}/*** 命令执行者*/static class Receiver{public void action() {System.out.println("command start");}}/*** 命令实现类*/static class CommandImpl implements Command{private Receiver mReceiver;public CommandImpl(Receiver receiver) {mReceiver = receiver;}@Overridepublic void exe() {mReceiver.action();}}/*** 测试* @param args*/public static void main(String[] args) {Invoker invoker = new Invoker(new CommandImpl(new Receiver()));invoker.action();}}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com