设计模式随记

一.设计原则

1.单一职责

就一个类而言,应该仅有一个引起它变化的原因。一个类的功能要单一,最好只承担一个职责。如果能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
当然设计原则,只是一种理论上最美好的实现,如果一个类只有一个方法,那么每次单独创建一个类有点耗费资源,我们可以将同样职责类的方法放在一起,当需求变更时只需添加新的方法。

2.开放封闭原则

对类可以扩展,但是不可修改,即面对新的需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。

当然无论模块是多么封闭,都会存在一些无法对之封闭的变化。既然不可能完全封闭,设计人员必须对于他设计的模块应该对哪种变化封闭做出选择。他必须先猜测出最有可能发生的变化种类,然后构造抽象来隔离哪些变化。

开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意地进行抽象同样不是一个好主意,拒绝不成熟的抽象和抽象本身一样重要。

3.依赖倒转原则

高层模块不应该依赖底层模块。两个都应该依赖抽象

抽象不应该依赖细节,细节应该依赖抽象

针对接口编程,不要对实现编程。

即不要让一个类单独的存在,最好有一个接口或者抽象类,这样在做扩展的时候,更加方便。

因为同样的一种动作可能会有多个类去做,只不过其实现的细节不一样,我们只需定义规则,之后具体的实现由各个子类自己完成,在调用时调用方也只需要根据规则去调用即可。
示例代码

public class DependecyInversion1 {
    public static void main(String[] args) {
        Person person=new Person();
        person.receive(new Email());
    }
}

class Email{
    public String getInfo(){
        return "返回消息:hello,world";
    }
}

/**
 * 方式一:
 *    1.Person类中的receive直接和Email发生依赖
 *    2.导致难以扩展,如现在要添加一个weixin返回消息,
 *      则在Person类中又需要添加一个方法
 *    3.receive方法最好依赖于接口或抽象类
 */
class Person{
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}
public class DependecyInversion2 {
    public static void main(String[] args) {
        Person1 person = new Person1();
        person.receive(new Email1());
        person.receive(new Weixin());
    }
}

//定义一个接口
interface IReceive {
    String getInfo();
}

class Email1 implements IReceive {
    public String getInfo() {
        return "返回消息:hello,world";
    }
}

class Weixin implements IReceive {
    @Override
    public String getInfo() {
        return "返回消息:hello,weixin";
    }
}

/**
 * 方式二:
 * 1.Person类中的receive直接和IReceive发生依赖
 * 2.现在要添加一个weixin返回消息时,客户端和receive方法都不需要改变
 */
class Person1 {
    public void receive(IReceive iReceive) {
        System.out.println(iReceive.getInfo());
    }
}

4.里氏代换原则

一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。

子类型必须能够替换掉它们的父类型,子类具有父类所有非private的行为和属性。

上面的要求子类需要满足,不去重写父类中的任何方法,如果继承了父类,而又重写了其中的方法,可以选择解除耦合的,采用聚合、组合、依赖的方式去使用其中一些公用的方法。

5.接口隔离原则

使用多个专门的接口,而不是用单一的总接口,即客户端不应该依赖那些它不需要的接口。

在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致接口泛滥,不利于维护。

接口也不能太大,太大的接口将违背隔离原则,灵活性较差。

即类不用去实现自己用不到的方法,保证接口的粒度。
示例代码

public class Segregation1 {
    public static void main(String[] args) {
        C c=new C();
        c.depend1(new A());
        c.depend2(new A());
        c.depend3(new A());

        D d=new D();
        d.depend1(new B());
        d.depend4(new B());
        d.depend5(new B());
    }
}

//将接口的粒度变小
interface Interface1{
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}

class A implements Interface1 {
    @Override
    public void operation1() {
        System.out.println("类A实现operation1");
    }

    @Override
    public void operation2() {
        System.out.println("类A实现operation2");
    }

    @Override
    public void operation3() {
        System.out.println("类A实现operation3");
    }

    @Override
    public void operation4() {
        System.out.println("类A实现operation4");
    }

    @Override
    public void operation5() {
        System.out.println("类A实现operation5");
    }
}

class B implements Interface1 {
    @Override
    public void operation1() {
        System.out.println("类B实现operation1");
    }

    @Override
    public void operation2() {
        System.out.println("类B实现operation2");
    }

    @Override
    public void operation3() {
        System.out.println("类B实现operation3");
    }

    @Override
    public void operation4() {
        System.out.println("类B实现operation4");
    }

    @Override
    public void operation5() {
        System.out.println("类B实现operation5");
    }
}

//类C通过接口Interface1使用A类,但是只会使用到1,2,3方法
class C{
    public void depend1(Interface1 interface1){
        interface1.operation1();
    }

    public void depend2(Interface1 interface1){
        interface1.operation2();
    }

    public void depend3(Interface1 interface1){
        interface1.operation3();
    }
}

//类D通过接口Interface1使用B类,但是只会使用到1,4,5方法
class D{
    public void depend1(Interface1 interface1){
        interface1.operation1();
    }

    public void depend4(Interface1 interface1){
        interface1.operation4();
    }

    public void depend5(Interface1 interface1){
        interface1.operation5();
    }
}

实现接口时,具体实现只需关心自己所需的方法,自己不需要的方法,可将接口中的方法移到共用接口中去。

public class Segregation2 {
    public static void main(String[] args) {
        C c=new C();
        c.depend1(new A());
        c.depend2(new A());
        c.depend3(new A());

        D d=new D();
        d.depend1(new B());
        d.depend4(new B());
        d.depend5(new B());
    }
}

interface Interface1{
    void operation1();
}

interface Interface2{
    void operation2();
    void operation3();
}

interface Interface3{
    void operation4();
    void operation5();
}

class A implements Interface1,Interface2{
    @Override
    public void operation1() {
        System.out.println("类A实现operation1");
    }

    @Override
    public void operation2() {
        System.out.println("类A实现operation2");
    }

    @Override
    public void operation3() {
        System.out.println("类A实现operation3");
    }
}

class B implements Interface1,Interface3{
    @Override
    public void operation1() {
        System.out.println("类B实现operation1");
    }

    @Override
    public void operation4() {
        System.out.println("类B实现operation4");
    }

    @Override
    public void operation5() {
        System.out.println("类B实现operation5");
    }
}
//类C通过接口Interface1使用A类,但是只会使用到1,2,3方法
class C{
    public void depend1(Interface1 interface1){
        interface1.operation1();
    }

    public void depend2(Interface2 interface2){
        interface2.operation2();
    }

    public void depend3(Interface2 interface2){
        interface2.operation3();
    }
}

//类D通过接口Interface1使用B类,但是只会使用到1,4,5方法
class D{
    public void depend1(Interface1 interface1){
        interface1.operation1();
    }

    public void depend4(Interface3 interface3){
        interface3.operation4();
    }

    public void depend5(Interface3 interface3){
        interface3.operation5();
    }
}

6.迪米特法则

一个软件实体应当尽可能少地与其他实体发生相互作用。

当其中某一个模块发生修改时,尽量少地影响其他模块,这样扩展会相对容易,这是对软件实体之间通信的限制。如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。

一个类最好只和他的"直接朋友"去联系,至于这个类中的具体实现应该知道的越少越好,尽可能的将其余陌生类的方法逻辑,封装到他们共同的"直接朋友类中"去,我们只要通直接朋友去访问封装的方法即可。

如果有个类A,他的直接朋友为类B,A中有一个局部变量使用到了陌生类C,而C和B也是直接朋友类,则将A中使用到的C类逻辑,封装到B中,A中即可通过B去引用封装好的方法。

直接朋友:当成员变量、方法参数、方法返回值中的类称为直接的朋友;陌生的类最好不要以局部变量的形式出现在类的内部。

二.设计模式

1.简单工厂模式

简单工厂模式将创建具体对象的代码封装起来了,通过用户输入的类型,在后台判断需要创建那些对象,优点是客户端无需关注对象的创建,缺点是不易扩展,每增加一个类,就要增加一个判断,违反了开放关闭原则。

public class CheesePizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("给奶酪披萨的制作准备原材料");
    }
}
public class GreekPizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("给希腊披萨类的制作准备原材料");
    }
}
public abstract class Pizza {
    //披萨的名称
    public String name;

    //准备
    public abstract void prepare();

    public void bake(){
        System.out.println(name+" 披萨烘培中");
    }

    public void cut(){
        System.out.println(name+" 披萨切割中");
    }

    public void box(){
        System.out.println(name+" 披萨装盒中");
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class OrderPizza {
    /*
         当前存在的问题在于:
            如果订单Pizza类在多几个,都使用了下面的判断的方法去创建一个披萨类
            这时一旦披萨需要添加一个种类,造成的修改将不止一点,我们应当将修改
            尽量变少,创建一个工厂类专门创建披萨类,每次修改也就修改这一个地方
     */
    public OrderPizza(){
        Pizza pizza=null;
        do {
            String type = getOrderType();
            if("cheese".equals(type)){
                pizza=new CheesePizza();
                pizza.setName("奶酪");
            }else if("greek".equals(type)){
                pizza=new GreekPizza();
                pizza.setName("希腊");
            }else{
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    public String getOrderType() {
        System.out.println("请输入");
        Scanner scanner = new Scanner(System.in);
        return scanner.next();
    }
}

2.策略模式

基于工厂方法,定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
策略模式在实践中,可以封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的策略,策略模式封装了变化。
策略模式主要是解决一个方法,会有多种不同的实现,客户端只需申明具体的实现类调用该方法,方法内的算法不用去管,当有新的实现时,也只需要多加一个类同样的来实现该方法,最终得出的结果是不同算法运算后的结果。
代码示例:

 */
public class CashContext {
    private CashSuper cashSuper=null;

    //根据需要的类型,来生成对应的收费情况
    public CashContext(String type) {
        if("正常收费".equals(type)){
            this.cashSuper=new CashNormal();
        }else if("满300返100".equals(type)){
            this.cashSuper=new CashReturn(300,100);
        }else if("打八折".equals(type)){
            this.cashSuper=new CashRebate(0.8);
        }
    }

    //返回应收的钱数
    public double getResult(double money){
        return cashSuper.acceptCash(money);
    }
}
//现金收费抽象类
public abstract class CashSuper {
    //收取现金,参数为原价,返回为当前价
    public abstract double acceptCash(double money);
}
public class CashNormal extends CashSuper {

    //正常收费原价返回
    @Override
    public double acceptCash(double money) {
        return money;
    }
}
//打折收费
public class CashRebate extends CashSuper {
    //折扣率
    private double moneyRebate=1;

    public CashRebate(double moneyRebate){
        this.moneyRebate=moneyRebate;
    }

    @Override
    public double acceptCash(double money) {
        return money*moneyRebate;
    }
}
//满moneyCondition返moneyReturn
public class CashReturn extends CashSuper{
    private double moneyCondition=0;
    private double moneyReturn=0;

    public CashReturn(double moneyCondition, double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double money) {
        return money-Math.floor(money/moneyCondition)*moneyReturn;
    }
}

3.装饰模式

InputStream中也使用了该模式。

动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活。装饰类拥有具体的实现类的实例,执行实现类的方式之前,可以加上特有的方法。

装饰模式把所需的功能按正确的顺序串联起来进行控制利用SetComponent类似的方法,来对对象进行包装的,这样每个装饰对象的实现就和如何使用这个对象分离开了,每个装饰对象只关心自己的功能,不需要关心如何被添加到对象链当中。

具体的对象和抽象的装饰类继承统一一个类或接口,通过装饰类包装具体的对象来加强具体对象的功能,装饰类实现的就是动态的增强功能。

代码示例

下面代码中具体的对象为Coffee类,装饰类为各类调味品,他们的祖先都是同一个类,调味品类可以不停的包装调味品类或者具体的咖啡类,在最后计算价格的时候,通过迭代调用统一的计算价格的方法,完成订单。

/**
 * 抽象的类:单品咖啡和调味品都要继承的类
 */
public abstract class Drink {
    public String des;//描述
    private float price=0.0f;

    public String getDes() {
        return des;
    }

    public void setDes(String des) {
        this.des = des;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }

    //计算价格的方法,由具体子类去实现
    public abstract float cost();
}

/**
  单品咖啡父类
*/
public class Coffee extends Drink {
    @Override
    public float cost() {
        //咖啡就是本身的价格
        return super.getPrice();
    }

    @Override
    public String getDes() {
        return super.getDes() + " " + cost();
    }
}
/**
 具体的咖啡类
*/
public class LongBlackCoffee extends Coffee {
    public LongBlackCoffee() {
        setDes("longblack");
        setPrice(5.0f);
    }
}
public class ShortBlackCoffee extends Coffee {
    public ShortBlackCoffee() {
        setDes(" shortblack");
        setPrice(4.5f);
    }
}
/**

 * 装饰者的抽象类
 */
public class Decorator extends Drink {
    //持有一个被装饰的实例
    private Drink drink;

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    @Override
    public float cost() {
        /*
          自己的价格+被装饰的价格
          如果drink目前也是一个装饰者的子类
              drink.cost()会递归增加
              一层一层的叠加,直到dirnk为被装饰者的类时
              递归停止,获得被装饰咖啡的价格,返回总价格
         */
        return super.getPrice() + drink.cost();
    }

    @Override
    public String getDes() {
        //输出被装饰者的信息
        return super.getDes() + " " + super.getPrice() + "&&" + drink.getDes();
    }
}
/**
 具体的装饰类
*/
public class Milk extends Decorator {
    public Milk(Drink drink) {
        super(drink);
        setDes("牛奶");
        setPrice(1.5f);
    }
}
public class Chocolate extends Decorator {
    public Chocolate(Drink drink) {
        super(drink);
        setDes("巧克力");
        setPrice(3.5f);
    }
}

/**
 客户端
*/
public class Client {
    public static void main(String[] args) {
        Drink order=new LongBlackCoffee();
        //加一份牛奶
        order=new Milk(order);
        //加一份巧克力
        order=new Chocolate(order);
        //加一份巧克力
        order=new Chocolate(order);
        System.out.println(order.cost());
        System.out.println(order.getDes());
    }
}

4.代理模式

为其他对象提供一种代理以控制对这个对象的访问,代理模式其实就是在访问对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

动态代理的本质就是重组字节码。

静态代理:

优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。

缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都需要维护。

动态代理:

1.代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。

2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象。

3.动态代理也叫做:JDK代理、接口代理。

 1.目标对象需要实现接口,用JDK代理

 2.目标对象不需要实现接口,用Cglib代理

使用场合:

1.远程代理,也就是为一个对象在不同地址空间提供局部代表。这样可以隐藏一个对象存在于不同地址空间的事实在Windows下,我们会为软件建立一些桌面的快捷方式,通过快捷方式来打开软件。

2.虚拟代理,是根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象比如说你打开一个很大的HTML网页时,里面可能有很多的文字和图片,但你还是可以很快打开它,此时你所看到的是所有的文字,但图片却是一张一张地下载后才能看到。那些未打开的图片框,就是通过虚拟代理来替代了真实的图片,此时代理存储了真实图片的路径和尺寸。

3.安全代理,用来控制真实对象访问时的权限 一般用于对象应该有不同的访问权限的时候。

4.智能指引,是指当调用真实的对象时,代理处理另外一些事如计算真实对象的引用次数,这样当该对象没有引用时,可以自动释放它;或当第一次引用一个持久对象时,将它装入内存;或在访问一个实际对象时,检查是否已经锁定它,以确保其他对象不能改变它。它们都是通过代理在访问一个对象时附加一些内务处理。

5.工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到子类。

简单工厂模式:违背了开放——封闭原则,增加新的需求时,需要改变if里面的判断来创建,不过保持了封装对象创建过程的优点。

工厂方法模式:保持了简单工厂模式的优点,而且克服了他的缺点,但缺点是每增加一个产品,就要增加一个产品的工厂,造成类过多的情况。

工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。

还有,如果一个类有多个构造方法(构造的重写),我们也可以将它抽出来,放到工厂中,一个构造方法对应一个工厂方法并命名一个友好的名字,这样我们就不再只是根据参数的不同来判断,而是可以根据工厂的方法名来直观判断将要创建的对象的特点。这对于使用者来说,体验比较好。

6.原型模式

Spring源码中的getBean方法使用到了原型模式,当你xml文件中的bean标签scopy属性为Prototype时用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

普通的赋值只是将引用给了另一对象,改变一个则所有的值都被改变。原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节。

如果使用普通new去创建多个方法,虽然简单,但是原本的类中发生改动的话,如加个属性或者减少个属性,你就需要改变所有使用new的方法了,而使用原型方法只需要改变一次即可。

这里不介绍clone方法,clone毕竟是浅复制,如果一个对象中包含一个对象,那么包含对象其实还是引用传递,虽然你也可以通过在包含对象实现clone方法来达到深复制,但是如果包含的对象过多的话,未免有些麻烦,这里介绍序列化和反序列化来达到原型模式。

记住实现Serializable接口,拷贝对象和包含的对象都要实现这个接口。

//实现深克隆方式二:采用序列化方法
    public Resume deepClone() {
        Resume resume = null;

        ObjectOutputStream oos = null;
        ByteArrayOutputStream bos = null;
        ObjectInputStream ois = null;
        ByteArrayInputStream bis = null;
        try {
            //将对象序列化出,会将其中的引用类型也序列化出去
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            //反序列化,得到初始值一致地址不一致的一个对象
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            resume = (Resume) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
                oos.close();
                bis.close();
                bos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return resume;
    }

7.模板方法模式

SpringIOC初始化源码中使用到了这种模式。

当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤更详细的层次上的实现不同时使用模板方法定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法模式是通过把不变行为搬移到超类,去除子类中的重复代码来体现它的优势,子类和父类的行为流程一致,只不过实现某些方法的细节不对,整个流程在模板方法中定义好了。

代码示例

该示例为制作豆浆的过程,总的来说制作豆浆的操作流程大致是相同的,只不过在豆浆添加材料时会有所区别。

/**
 * 制作豆浆的流程:选材--->添加配料--->浸泡--->放到豆浆机打碎
 * 整个过程就是添加配料不一致
 */
public abstract class SoyaMilk {
    //模板方法
    final void make() {
        select();
        addCondiments();
        soak();
        beat();
    }
    protected abstract void addCondiments();

    protected void beat() {
        System.out.println("第四步:放到豆浆机打碎");
    }

    protected void soak() {
        System.out.println("第三步:浸泡");
    }

    //选材
    protected void select() {
        System.out.println("第一步:选材");
    }
}
public class PeanutSoyaMilk extends SoyaMilk {
    @Override
    protected void addCondiments() {
        System.out.println("添加花生");
    }
}
public class RedBeanSoyaMilk extends SoyaMilk {
    @Override
    protected void addCondiments() {
        System.out.println("添加红豆");
    }
}
public class Client {
    public static void main(String[] args) {
        System.out.println("-------红豆豆浆制作------");
        SoyaMilk soyaMilk1 = new RedBeanSoyaMilk();
        soyaMilk1.make();
        System.out.println("-------花生豆浆制作------");
        SoyaMilk soyaMilk2 = new PeanutSoyaMilk();
        soyaMilk2.make();
    }
}

8.观察者

定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

当一个对象的改变需要同时改变其他对象的时候,观察者模式所作的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。

举个例子:在拍卖行的时候,拍卖师就是一个主题对象,顾客就是众多的观察者,那么当有其中一个顾客加价时,拍卖师需要更新商品的状态,并且把最新的价格反馈给每个顾客,顾客再根据商品的最新价格来决定直接是否加价。

代码示例:

该示例中老板为主题对象,员工为观察者,老板不在时,员工在看股票,当老板回来时所有员工都需要修改自己的状态,将股票关闭开始工作。

//老板类,具体的主题实现类
public class Boss implements Subject{
    //存储观察者
    public List<Observer> observers=new ArrayList<>();

    //将观察者添加进入
    @Override
    public void add(Observer observer) {
        observers.add(observer);
    }

    //将观察者删除
    @Override
    public void delete(Observer observer) {
        observers.remove(observer);
    }

    //通知所有观察者
    @Override
    public void notifyAllObserver() {
        for(Observer observer:observers){
            observer.update();
        }
    }

    //自身发生改变
    public void update(){
        System.out.println("我回来啦");
        notifyAllObserver();
    }
}
public class StockObserver implements Observer{
    @Override
    public void update() {
        System.out.println("老板回来了,关掉行情");
    }
}

9.抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

优点

便是易于交换产品系列,由于具体工厂类,在一个应用中只需在初始化的时候一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。

它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。

抽象工厂类解决了工厂方法造成类过多的问题,一般适用于相同产品簇的之间创建,如有两个数据库类MySQL和SQLServer,内部都有两张表User和Department也就是两个具体实例,这时候创建的两个工厂类,分别是MySQL工厂类和SQLServer工厂类,并且都实现了父类的工厂接口,创建两张表,这里MySQL和SQLServer就是同一产品簇。

10.状态模式

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

当一个类随着另一个类的状态而改变时,减少条件判断,状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。

将功能中可能出现的状态都抽象成一个类,在这个状态里去实现他拥有的行为逻辑,之后改变内部状态,变为下一个状态,这样就不用判断每个状态,内部的状态随着行为来自己变换。

应用场景
当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式。工作日,随着时间改变工作状态。

public interface State {
    void WriteProgram(WorkDay work);
}
public class WorkDay {
    //根据时间来转化状态
    public int hour;

    public State state=new DayTime();

    public void  WriteProgram(){
        state.WriteProgram(this);
    }
}
//白天的工作精神好
public class DayTime implements State {
    @Override
    public void WriteProgram(WorkDay work) {
        //如果时间大于12点,进入下午的状态
        if(work.hour<=12){
            System.out.println("白天工作精神好");
        }else {
            new Afternoon().WriteProgram(work);
        }
    }
}
public class Afternoon implements State {
    @Override
    public void WriteProgram(WorkDay work) {
        //时间小于7点进入夜晚
        if (work.hour<=19) {
            System.out.println("下午工作时间想睡觉");
        }else {
            new Night().WriteProgram(work);
        }
    }
}
public class Night implements State {
    //判断时间是否加班
    @Override
    public void WriteProgram(WorkDay work) {
        if(work.hour<21){
            System.out.println("回家睡觉...");
        }else{
            System.out.println("加班中...");
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        WorkDay workDay = new WorkDay();
        //改变时间
        workDay.hour=8;
        workDay.WriteProgram();
        workDay.hour=13;
        workDay.WriteProgram();
        workDay.hour=21;
        workDay.WriteProgram();
    }
}

11.适配器模式

SpringMVC中的HandlerAdapter就使用到了该模式。 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能 一起工作的那些类可以一起工作。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。
类适配器:适配器类继承源类、实现目标接口。
对象适配器:适配器实现目标接口,持有源类的实例。
接口适配器:适配器实现源接口中所有的空方法,目标类继承后,根据选择去重写特定方法。

应用场景:
如手机充电,手机只能接受5V的电压,而我们常用的是220V的电压,适配器就是我们的充电线,将220V的电压转换为了5V的电压,这里的220V的电压就是我们需要适配的类。

/**
* 电压5V的接口
*/
public interface IVoltager5V {
    int output5V();
}
/**
* 手机只能接收5V的电压
*/
public class Phone {
   //充电
   public void charging(IVoltager5V iVoltager5V){
       if(iVoltager5V.output5V()==5){
           System.out.println("电压符合,开始充电");
       }
   }
}
/**
* 源类:220V电压
*/
public class Voltager220 {
   private int voltager = 220;

   public int output220V() {
       int src = 220;
       System.out.println("电压=" + src + "伏");
       return src;
   }
}
/**
* 适配器类:可以将现有的220V的电压转化为5V,提供给手机充电
*/
public class VoltagerAdapter extends Voltager220 implements IVoltager5V {
   @Override
   public int output5V() {
       int src = output220V();
       int dst = src / 44;
       System.out.println("电压从:"+src+"V,转化为:"+dst+"V");
       return dst;
   }
}
/**
* 类适配器
*/
public class Client {
   public static void main(String[] args) {
      Phone phone=new Phone();
      phone.charging(new VoltagerAdapter());
   }
}

12.备忘录模式

备忘录在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态,保存对象的某一个状态,可以随时恢复。

应用场景
Memento模式比较适用于功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分,Originator可以根据保存的Memento信息还原到前一状态。

发起人,Originator

//游戏角色需要被保存对象
public class GameRole {
  public int vit;
  public int atk;
  public int def;

  //保存角色状态
  public RoleStateMemento saveState(){
      return (new RoleStateMemento(vit,atk,def));
  }

  //恢复角色状态
  public void RecoveryState(RoleStateMemento memento){
       this.vit=memento.getVit();
       this.atk=memento.getAtk();
       this.def=memento.getDef();
  }

  @Override
  public String toString() {
      return "GameRole{" +
              "vit=" + vit +
              ", atk=" + atk +
              ", def=" + def +
              '}';
  }
}

备忘录,要保存的细节给封装在Memento中

//备忘录类,角色状态存储箱类
public class RoleStateMemento {
 public int vit;
 public int atk;
 public int def;

 public RoleStateMemento(int vit, int atk, int def) {
     this.vit = vit;
     this.atk = atk;
     this.def = def;
 }
}

管理类,保存备忘录,通过管理类,可以得到备忘录的内容

//角色状态管理者类
public class RoleStateCaretaker {
 private RoleStateMemento memento;

 public RoleStateMemento getMemento() {
     return memento;
 }

 public void setMemento(RoleStateMemento memento) {
     this.memento = memento;
 }
}

13.组合模式

将对象组合成树形结构以表示部分—整体的层次结构,组合模式使得用户对单个对象的组合对象的使用具有一致性。 组合模式类似于二叉数,多个叶节点和枝节点组合起来形成的结构。整体与部分可以被一致对待,对于word文档里的文字,对单个字的处理和对多个字,甚至整个文档的处理。

应用场景
当你发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑用组合模式了。

如下:具体的学校类和院系都是枝节点,拥有增加和删除叶节点的功能,以及打印自身和自身拥有的叶节点,具体的专业就是叶节点只能打印自身。也可适用于公司和部门直接的关系。

/**
 * 枝节点和叶节点的共同抽象类
 */
public abstract class OrganizationComponent {
    private String name;

    public OrganizationComponent(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //add和remove方法,叶节点中并不需要实现,不用实现为抽象方法
    public void add(OrganizationComponent organizationComponent){
        throw new UnsupportedOperationException();
    }

    public void remove(OrganizationComponent organizationComponent){
        throw new UnsupportedOperationException();
    }

    public abstract void print();
}

/**
 * 学校类,枝节点管理院校
 */
public class University extends OrganizationComponent {
    List<OrganizationComponent> organizationComponents = new ArrayList<>();

    public University(String name) {
        super(name);
    }

    @Override
    public void add(OrganizationComponent organizationComponent) {
        organizationComponents.add(organizationComponent);
    }

    @Override
    public void remove(OrganizationComponent organizationComponent) {
        organizationComponent.remove(organizationComponent);
    }

    @Override
    public void print() {
        System.out.println("---------" + getName() + "--------");
        for (OrganizationComponent organizationComponent : organizationComponents) {
            organizationComponent.print();
        }
    }
}

/**
 * 枝节点:院校类,管理专业类
 */
public class College extends OrganizationComponent {
    List<OrganizationComponent> organizationComponents = new ArrayList<>();

    public College(String name) {
        super(name);
    }

    @Override
    public void add(OrganizationComponent organizationComponent) {
        organizationComponents.add(organizationComponent);
    }

    @Override
    public void remove(OrganizationComponent organizationComponent) {
        organizationComponent.remove(organizationComponent);
    }
    @Override
    public void print() {
        System.out.println("---------" + getName() + "--------");
        for (OrganizationComponent organizationComponent : organizationComponents) {
            organizationComponent.print();
        }
    }
}

/**
 * 系专业类,叶节点
 */
public class Department extends OrganizationComponent {
    public Department(String name) {
        super(name);
    }

    @Override
    public void print() {
        System.out.println(getName());
    }
}

/**
 * 组合模式
 */
public class Client {
    public static void main(String[] args) {
        OrganizationComponent university=new University("清华大学");

        OrganizationComponent college1=new College("计算机学院");
        OrganizationComponent college2=new College("信息工程学院");

        OrganizationComponent depart1=new Department("软件工程");
        OrganizationComponent depart2=new Department("网络工程");
        OrganizationComponent depart3=new Department("通信工程");
        OrganizationComponent depart4=new Department("信息工程");

        //将系专业添加进院校,将院校添加进学校,构造树结构
        college1.add(depart1);
        college1.add(depart2);
        college2.add(depart3);
        college2.add(depart4);

        university.add(college1);
        university.add(college2);

        //打印关系
        university.print();
    }
}

14.迭代器模式

提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以外部代码透明地访问集合内部的数据。为每种不同的聚合对象,提供一个一致性迭代的方法,统一操作。

应用场景
对于不同的容器对外提供一个一致性的方法,用于遍历容器中存储的元素,用户不用关系具体的实现,只需要使用即可。

//抽象迭代器,允许不同的迭代对象
public interface Iterator {
    Object First();
    boolean IsDone();
    Object CurrentItem();
}
//具体的迭代器类,正序遍历
public class ConcreteIterator implements Iterator {
    public List<Object> items=new ArrayList<>();
    //当前索引
    private int current=0;
    @Override
    public Object First() {
        return items.get(0);
    }

    @Override
    public boolean IsDone() {
        return current<items.size();
    }

    @Override
    public Object CurrentItem() {
        Object object=items.get(current);
        current=current+1;
        return object;
    }
}

15.单例模式

保证一个类仅有一个实例,并提供一个访问的它的全局访问点之后获得的对象都是同一个地址通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法使用场景:频繁创建和销毁的对象,以及一些创建十分消耗资源的对象。

应用场景:
单例模式的实现方式大概有七种,基本可以分为懒汉式和饿汉式,下面介绍一种常用的懒汉式方法。

public class Instance {
    /**
     * 双端检索机制不一定安全
     * 原因是有指令重排的存在,加入volatile可以禁止指令重排
     * 原因在于某一个线程执行到第一次检测,读取到的instance不为null时,
     * instance的引用对象可能没有完成初始化
     * <p>
     * instance=new SingletonDemo();可以分为以下3步完成
     * memory=allocate();//1.分配对象内存空间
     * instance(memory);//2.初始化对象
     * instance=menmory;//3.设置instance指向刚分配的内存地址,此时instance!=null
     * 正常的顺序如上,但是由于第二步和第三步不存在数据依赖关系,所以可能会重排
     * memory=allocate();//1.分配对象内存空间
     * instance=menmory;//3.设置instance指向刚分配的内存地址,此时instance!=null
     * instance(memory);//2.初始化对象
     * 这样会造成一条线程访问instance不为null(第一个判断)时,
     * 由于instance实例未必已初始完成,也就造成线程安全问题
     */
    private static volatile Instance instance = null;

    private Instance() {
    }

    public static Instance getInstance() {
        if (instance == null) {
            synchronized (Object.class) {
                if (instance == null) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

16.桥接模式

源码:Driver中实现了该模式。将抽象部分与它的实现部分分离,使它们都可以独立地变化。 实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。正确的去实现系统中的两个独立变化维度。

应用场景:

如手机会有很多不同的品牌,每个手机上都会有不同的应用程序,如果一个应用程序发生了变化,所有安装了该应用程序的手机都需要去变化,这时候我们就可以将手机和应用程序分离成两个不同的主题,各自变化。

//手机品牌
public abstract class HandsetBrand {
    //连接手机软件类
    public HandsetSoft soft;

    //设置手机软件
    public void SetHandsetSoft(HandsetSoft soft){
        this.soft=soft;
    }

    //运行
    public abstract void Run();
}
//具体实现类,手机品牌M
public class HandsetBrandM extends HandsetBrand {
    @Override
    public void Run() {
       soft.Run();
    }
}
//手机软件
public abstract class HandsetSoft {
    //运行软件
    public abstract void Run();
}
//部分具体类,手机软件实现类
public class HandsetBrandM extends HandsetBrand {
    @Override
    public void Run() {
       soft.Run();
    }
}

17.命令模式

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

1.命令模式能较容易地设计一个命令队列

2.在需要的情况下,可以较容易地将命令记入日志

3.允许接收请求的一方决定是否要否决请求

4.可以容易地实现对请求的撤销和重做

5.由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。

命令模式将把请求一个操作的对象与知道怎么执行一个操作的对象分割开。每个具体的命令类其实就是调用执行者中的每个方法,将执行者和接收者解耦。

应用场景:
将所有的请求全部规范化的管理,所有的请求都只调用一个方法,具体的实现都在执行者中,例如烧烤店的例子,客户的所有请求都交由服务员来管理,执行者对服务员提交的命令队列做处理,无论是新增还撤销请求都会很简单。

//抽象命令类
public abstract class Command {
    protected Barbecuer receiver;

    public Command(Barbecuer receiver) {
        this.receiver = receiver;
    }

    //执行命令
    abstract public void ExcuteCommand();
}
//烤鸡翅命令,具体命令的调用者
public class BakeChickenWingCommand extends Command {
    public BakeChickenWingCommand(Barbecuer receiver) {
        super(receiver);
    }

    @Override
    public void ExcuteCommand() {
        receiver.BakeChickenWing();
    }
}
public class BakeMuttonCommand extends Command {

    public BakeMuttonCommand(Barbecuer receiver) {
        super(receiver);
    }

    @Override
    public void ExcuteCommand() {
         receiver.BakeMutton();
    }
}
//烤肉串者,命令执行者
public class Barbecuer {
    //烤羊肉
    public void BakeMutton(){
        System.out.println("烤羊肉串!");
    }

    //烤鸡翅
    public void BakeChickenWing(){
        System.out.println("烤鸡翅!");
    }
    
}
//服务员类,代理命令接收的类
public class Waiter {
    //设置订单,存储多个命令
    private List<Command> orders=new ArrayList<>();

    //设置订单
    public void SetOrder(Command command){
        if(command.toString().equals("nothing")){
            System.out.println("服务员:鸡翅没有了,请点别的烧烤");
        }else{
            orders.add(command);
            System.out.println("增加订单:"+command.toString()+"时间:"+new Date().toString());
        }
    }

    //取消订单
    public void CancelOrder(Command command){
        orders.remove(command);
        System.out.println("取消订单:"+command.toString()+"时间:"+new Date().toString());
    }

    //全部执行命令
    public void Notify(){
        for (Command order : orders) {
            order.ExcuteCommand();
        }
    }
}

18.职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。使得接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。仅需保持一个指向其后继者的引用,而不需保持它所有的候选接收者的引用。当客户提交一个请求时,请求是沿链传递直至有一个ConcreteHandler对象负责处理它。

应用场景:
非常适用于上下级的关系,例如在公司提交请假或加薪申请时,需要一级一级的往上提交审核,直到有一个具体的处理者处理掉,该模式和状态模式非常类似,后面会解释两者的不同。

//发送请求
public class Request {
    //请求类别
    private String requestType;
    //申请内容
    private String requestContent;
    //数量
    private int number;


    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getRequestContent() {
        return requestContent;
    }

    public void setRequestContent(String requestContent) {
        this.requestContent = requestContent;
    }

    public String getRequestType() {
        return requestType;
    }

    public void setRequestType(String requestType) {
        this.requestType = requestType;
    }
}
//管理者
public abstract class Manager {
    protected String name;
    protected Manager superior;

    public Manager(String name) {
        this.name = name;
    }

    //设置管理者的上级
    public void SetSuperior(Manager superior){
        this.superior=superior;
    }

    //申请请求
    abstract void RequestApplications(Request request);
    
}
//经理
public class CommonManager extends Manager{
    public CommonManager(String name) {
        super(name);
    }

    @Override
    void RequestApplications(Request request) {
        //经理所能有的权限就是可准许下属两天的假期
        if(request.getRequestType().equals("请假")&&request.getNumber()<=2){
            System.out.println(name+":"+request.getRequestContent()+"数量"+request.getNumber()+"被批准");
        }else {
            //其余申请转交给上级
            if(superior!=null){
                superior.RequestApplications(request);
            }
        }
    }
}
//总监
public class Majordomo extends Manager{
    public Majordomo(String name) {
        super(name);
    }

    @Override
    void RequestApplications(Request request) {
       //总监所能的权限就是可准许下属一周内的假期
        if(request.getRequestType().equals("请假")&&request.getNumber()<=5){
            System.out.println(name+":"+request.getRequestContent()+"数量"+request.getNumber()+"被批准");
        }else{
            if(superior!=null){
                superior.RequestApplications(request);
            }
        }
    }
}
//总经理
public class GeneralManager extends Manager {

    public GeneralManager(String name) {
        super(name);
    }

    @Override
    void RequestApplications(Request request) {
        //总经理可处理全部的请求
        if (request.getRequestType().equals("请假")) {
            System.out.println(name+":"+request.getRequestContent()+"数量"+request.getNumber()+"被批准");
        }else if(request.getRequestType().equals("加薪")&&request.getNumber()<=500){
            System.out.println(name+":"+request.getRequestContent()+"数量"+request.getNumber()+"被批准");
        }else if(request.getRequestType().equals("加薪")&&request.getNumber()>500){
            System.out.println(name+":"+request.getRequestContent()+"数量"+request.getNumber()+"再说把");
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        CommonManager jinli = new CommonManager("维奇");
        Majordomo zongjian = new Majordomo("明月");
        GeneralManager zongjingli = new GeneralManager("耸立");
        jinli.SetSuperior(zongjian);
        zongjian.SetSuperior(zongjingli);
        //有时需要形成一个环状结构,否则如果从zongjingli开始就会报错

        Request request = new Request();
        request.setRequestType("请假");
        request.setNumber(1);
        request.setRequestContent("我想请假");
        jinli.RequestApplications(request);
    }
}

19.中介者模式

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

中介者模式,其实是将各个子系统的通信全部消除,变成一个个独立的模块,将它们的通信全部放在自己的一个方法里面,这样就会导致这个方法的判断过多,承载的责任也就越多。中介者需要先判断出这个模块的类型,然后在判断里调用其余的模块。

应用场景

优点

Mediator的出现减少了各个Colleague的耦合,使得可以独立地改变和复用各个Colleague类和Mediator,比如下例中任何国家的改变不会影响到其他国家,而只是与安理会发生变化,其次由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。

缺点

ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就是中介者会变得比任 何一个ConcreteMediator都复杂,ConcreteMediator需要了解所有的Colleague。

//联合国机构,相当于抽象的中介者
public abstract class UnitedNations {
    public abstract void Declare(String message,Country colleague);
}
//联合国安理会
public class UnitedNationsSecurityCouncil extends UnitedNations {
    private USA usa;
    private Iraq iraq;

    public USA getUsa() {
        return usa;
    }

    public void setUsa(USA usa) {
        this.usa = usa;
    }

    public Iraq getIraq() {
        return iraq;
    }

    public void setIraq(Iraq iraq) {
        this.iraq = iraq;
    }
    @Override
    public void Declare(String message, Country colleague) {
        if (colleague==usa) {
             iraq.GetMessage(message);
        }else {
             usa.GetMessage(message);
        }
    }
}
//抽象的国家
public abstract class Country {
    protected UnitedNations mediator;

    public Country(UnitedNations mediator) {
        this.mediator = mediator;
    }
}
//美国
public class USA extends Country{
    public USA(UnitedNations mediator) {
        super(mediator);
    }

    //声明
    public void Declare(String message){
        mediator.Declare(message,this);
    }

    //获得消息
    public void GetMessage(String message){
        System.out.println("美国获得对方信息:"+message);
    }  
}
//伊拉克
public class Iraq extends Country {
    public Iraq(UnitedNations mediator) {
        super(mediator);
    }

    //声明
    public void Declare(String message){
        mediator.Declare(message,this);
    }

    //获得消息
    public void GetMessage(String message){
        System.out.println("伊拉克获得对方信息:"+message);
    }
}

20.享元模式

运用共享技术有效地支持大量细粒度的对象,享元模式可以避免生成大量非常相似类的开销,在程序设计中,有时需要生成大量细粒度的类实例来表示数据,如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。

应用场景:

如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用。还有就是对象的大多数状态可以为外部状态(变化的状态),如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

例如下面的例子,很多网站中相同的分类其实有很多都是类似的,比如博客网站网站的样式都是一致的,唯一有所区别的就是每个博客所属的用户不同,这里的用户就是指外部状态。

//网站抽象类
public abstract class WebSite {
    public abstract void Use(User user);
}
//具体网站类
public class ConcreteWebSite extends WebSite {
    private String name="";

    public ConcreteWebSite(String name) {
        this.name = name;
    }

    @Override
    public void Use(User user) {
        System.out.println("网站分类:"+name+"用户:"+user.getName());
    }
}
//网站工厂
public class WebSiteFactory {
    private Map<String,WebSite> flyweights=new HashMap<>();
    //获得网站分类
    public WebSite GetWebSiteCategory(String key){
        if(!flyweights.containsKey(key)){
           flyweights.put(key,new ConcreteWebSite(key));
        }
        return flyweights.get(key);
    }

    //获得网站分类总数
    public int GetWebSiteCount(){
        return flyweights.size();
    }
}
//用户,外部状态,每个实例可能会变化的地方
public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(String name) {
        this.name = name;
    }
}

public class Demo {
    public static void main(String[] args) {
       WebSiteFactory wsf=new WebSiteFactory();

       WebSite ws=wsf.GetWebSiteCategory("产品展示");
       ws.Use(new User("三毛"));

       WebSite ws1=wsf.GetWebSiteCategory("产品展示");
       ws1.Use(new User("四毛"));

       WebSite ws2=wsf.GetWebSiteCategory("博客");
       ws2.Use(new User("五毛"));

        System.out.println("得到网站分类总数:"+wsf.GetWebSiteCount());//2
    }
}

21.解释器模式

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

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。

这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

优点:

容易地改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。 也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。

缺点:

解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。 建议当文法非常复杂时,使用其他的技术如语法分析程序或编译器生成器来处理。

22.访问者模式

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

它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。访问者模式的优点就是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。

访问者模式将有关的行为集中到一个访问者对象中。访问者适用于数据结构相对稳定的系统,比如性别,数据结构只有男女两种,改变需求只需要增加新的访问者类。

应用场景:

当你的数据结构是稳定不变时,数据结构中对应的操作就像是一个个访问者,对每一个数据机构都有一个与之对应的访问者。男人和女人在不同的状态下,都会有不同的反应,之后发生变化的就只有状态,我们也只需增加状态就可。

//人的抽象类
public interface Person {
    //接受
    void Accept(Action visitor);
}
public class Woman implements Person {
    @Override
    public void Accept(Action visitor) {
        visitor.GetWoManConclusion(this);
    }
}
//男人类
public class Man implements Person{

    @Override
    public void Accept(Action visitor) {
        visitor.GetManConclusion(this);
    }
}
//状态的抽象类
public interface Action {
    //得到男人结论或反应
    public abstract void GetManConclusion(Man concreteElementA);
    //得到女人结论或反应
    public abstract void GetWoManConclusion(Woman concreteElementA);
}
//成功
public class Success implements Action{
    @Override
    public void GetManConclusion(Man concreteElementA) {
        System.out.println(concreteElementA.getClass().getName()+this.getClass().getName()+"时,背后多半有一个伟大的女人");
    }

    @Override
    public void GetWoManConclusion(Woman concreteElementA) {
        System.out.println(concreteElementA.getClass().getName()+this.getClass().getName()+"时,背后大多有一个不成功的男人");
    }
}
//恋爱
public class Amativeness implements Action{
    @Override
    public void GetManConclusion(Man concreteElementA) {
        System.out.println(concreteElementA.getClass().getName()+this.getClass().getName()+"时,背后多半有一个伟大的女人");
    }

    @Override
    public void GetWoManConclusion(Woman concreteElementA) {
        System.out.println(concreteElementA.getClass().getName()+this.getClass().getName()+"时,背后大多有一个不成功的男人");
    }
}

23.外观模式

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

之前是多个客户类对应对多个实现类,当有一个实现类改变了代码,则所有的客户类都需要更改,现在只需要增加。一个外观类,多个客户类只要对应一个外观类即可,由外观类去对应多个实现类的实现,修改实现类只需要修改外观类

外观模式就像我们下载东西,点击一键安装就可以完成下载,而我们不用关心下载的具体实现,一键安装就像一个安装类,帮我们完成了具体的实现,我们只要关心这个提供的接口即可,即将具体实现的一系列的流程封装起来。

应用场景:

1.在设计初期阶段,应该要有意识的将不同的两个层分离,比如经典的三层架构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层和表示层的层与层之间建立外观,这样可以为复杂的子系统提供一个简单的接口。

2.在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观可以提供一个简单的接口,减少它们之间的依赖。

3.在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,可以为新系统开发一个外观类, 提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与外观对象交互,外观与遗留代码交互所有复杂的工作。

//基金类,负责帮客户购买和卖出股票
//可以提供多个买卖方案
public class Fund {
    //股票1、2、3
    Stock1 stock1=new Stock1();
    Stock2 stock2=new Stock2();
    Stock3 stock3=new Stock3();

    //购买
    public void buy(){
        stock1.buy();
        stock2.buy();
        stock3.buy();
    }

    //卖出
    public void sell(){
        stock1.sell();
        stock2.sell();
        stock3.sell();
    }
}
public class Stock1 {
    public void buy(){
        System.out.println("股票一购买");
    }

    public void sell(){
        System.out.println("股票一卖出");
    }
}
public class Demo {
    public static void main(String[] args) {
         //客户端只需要关注基金类即可
         Fund fund=new Fund();
         fund.buy();
         fund.sell();
    }
}

24.建造者模式

JDK源码中StringBuilder就是具体的建造者和指挥者结合,AbstractStringBuilder即为抽象建造者。

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。在构建一个人的时候,每一个人的基本构成是一致的,只不过根据外观、身材会有不同的区别。

用户只需指定需要建造的类型就可以得到他们,而具体建造的过程和细节就不需要知道了。 建造者将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

应用场景:

主要是用于创建一些复杂的对象,这些对象内部构建间的建造顺序通常是稳定的,但对象内部的构建通常面临复杂的变化。规定建造的顺序,具体的建造时的实现由子类完成。

public interface PersonBuilder {

    void createPersonBody();

    void createPersonFoot();

    void createPersonHand();

    void createPersonHead();
}
//指挥者,根据需求指挥建造一个人类
public class PersonDirector {
    PersonBuilder personBuilder;

    public PersonDirector() {
    }

    public PersonDirector(PersonBuilder personBuilder) {
        this.personBuilder = personBuilder;
    }
    //根据一个需求建造一个人类
    public void builderPerson(){
        personBuilder.createPersonHead();
        personBuilder.createPersonHand();
        personBuilder.createPersonFoot();
        personBuilder.createPersonBody();
    }
}
//建造一个人类A
public class PersonBuilderA implements PersonBuilder{

    @Override
    public void createPersonBody() {
        System.out.println("瘦子的身体");
    }

    @Override
    public void createPersonFoot() {
        System.out.println("瘦子的脚");
    }

    @Override
    public void createPersonHand() {
        System.out.println("瘦子的头");
    }

    @Override
    public void createPersonHead() {
        System.out.println("瘦子的手");
    }
}

三、代码无错就是优?

1.创建型模式

创建型模式隐藏了这些类的实例是如何被创建和放在一起,整个系统关于这些对象所知道的是由抽象类所定义的 接口,这样,创建型模式在创建了什么,谁创建它、它是怎么被创建的,以及何时创建这些方面提供了很大的灵活性。

2.结构性模式

在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。

3.行为型模式

在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,行为针对的是类中的方法的一个设计。

4.工厂方法模式和抽象工厂模式

工厂方法模式

每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式

每个具体工厂类可以创建多个具体产品类的实例,工厂方法模式是一种特殊的抽象工厂模式,工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。

工厂方式模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。 抽象工厂模式其实就是一种更易于扩展的工厂方法模式

5.适配器、装饰者和代理模式

适配器:将一个类通过某种方式转换成另一个类。

装饰模式:是在一原有类的基础上增加了某些新的功能变成另一个类。

代理模式:是将一个类转换成具体的类。

代理模式是与原对象实现同一个接口,而适配器则是匹配新接口,装饰模式注重的是功能的拓展。

适配器是将一个新增的接口适配到已有的接口中,适配器类需要实现和被适配类同样的接口,并持有已有类的实例。

代理模式注重隔离限制,让外部不能访问你实际的调用对象,在使用接口之前添加一些其余操作,代理模式一定是自身持有这个对象,不需要从外部传入。

装饰者模式注重的是功能的拓展,在同一个方法下实现更多的功能,装饰者模式一定是从外部传入这个对象,并且可以没有顺序。

代码示例:

原接口:

//原接口需要传入orderId,时间
public interface SourceOrderApi {
  public void updateDate(String orderId,String date,String client);
}

原接口实现类:

public class SourceOrderApiImpl implements SourceOrderApi{
    @Override
    public void updateDate(String orderId, String date, String client) {
        System.out.println(client+"已将订单"+orderId+"的有效期延迟至"+date);
    }
}

需要增加一个新接口,不用传入延长时间,但不能更换使用老接口的类:

//新的APP接口,不提供截至时间,只需提供订单号即可延长时间
public interface NewAppOrderApi {
  public void updateDate(String orderId,String client);
}

新的实现类:

//新的实现类,不需要传截至时间,直接延长时间
public class NewAppOrderApiImpl implements NewAppOrderApi {
    @Override
    public void updateDate(String orderId, String client) {
        System.out.println(client+"已将订单"+orderId+"的有效期延迟至9999-12-12");
    }
}

需要适配器,将其适配进老接口:

//实现原接口,但调用新接口
public class SourceOrderApiAdapter implements SourceOrderApi {
    private NewAppOrderApi newAppOrderApi=new NewAppOrderApiImpl();
    @Override
    public void updateDate(String orderId, String date, String client) {
        newAppOrderApi.updateDate(orderId,client);
    }
}

代理类,需要在调用方法前判断用户身份:

//代理订单类,加入身份验证,不可改变原接口的类
public class ProxySourceOrderApiImpl implements SourceOrderApi {
    SourceOrderApi sourceOrderApi=new SourceOrderApiImpl();
    @Override
    public void updateDate(String orderId, String date, String client) {
        //进行身份判断,如果是admin则允许下订单
        if("admin".equals(client)){
            sourceOrderApi.updateDate(orderId,date,client);
        }else{
            System.out.println("对不起没有权限,无法下订单");
        }
    }
}

装饰者类,需要拓展新的功能:

//丰富原有类的功能,不仅可以延长提货日期
//还可以延长退款日期
public class DecorateAppOrderApiImpl implements SourceOrderApi {
    SourceOrderApi sourceOrderApi;

    public DecorateAppOrderApiImpl(SourceOrderApi sourceOrderApi) {
        this.sourceOrderApi = sourceOrderApi;
    }

    @Override
    public void updateDate(String orderId, String date, String client) {
        sourceOrderApi.updateDate(orderId,date,client);
        System.out.println(client+"已将订单"+orderId+"的退款期延长至"+date);
    }
}

6.状态模式和职责链模式

职责链和状态模式的最大不同是设置自己的下一级的问题上,状态模式是在类的设计阶段就定好的,不能再客户端改变,而职责链的下一级是在客户端自己来确定的。

对比:

在类的设计阶段设定(状态模式)的好处是不用客户来确定下一状态,也就减少了客户设置错误的问题,客户也不用知道状态的具体结构,同时存在灵活性差,耦合度高的问题,顺序不能乱,而在客户端设定(职责链模式)要求客户对各个类的职责要有所了解,并能正确设置好职责链,会加大设置出错的风险,但是它比较灵活。

1.描述问题的角度不一样:状态模式是从用户的状态方面描述的;职责链模式是从管理者角度描述的,每个对象处理问题的结果都为两种情况是或否。

2.状态模式是类的不同状态的多种不同的反映;职责链模式是不同类对同一个问题的反映

3.状态模式的子类是从抽象类中分离出去的;职责链模式是针对一类问题的结构优化,在处理问题时森严。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值