JAVA设计模式学习

注:本文章是关于23种设计模式的学习,比较简单,易与上手,可以让读者大致了解java设计模式的基本概念以及使用,如果想深度了解,建议购买相关资料进行学习。

1 设计模式概述

软件设计模式:又称设计模式,是一套被反复使用,多人知晓的,经过系统分类编目的,代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决待定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性,继承性和多态性以及类的关联和组合关系的充分理解。
优点:
*提高程序员的思维能力,编码能力和设计能力。
*使程序设计更加的标准化,代码编写更加规划法,使得开发效率大大提高,从而缩短软件的开发周期。
*使设计的代码可重用性高,可读性高,可维护性高。

设计模式分类:
创建型模式:单例模式,原型模式,抽象工厂模式,工厂方法模式,建造者模式。
结构型模式:代理模式,适配器模式,桥接模式,外观模式,组合模式等。
行为型模式:命令模式,责任链模式,观察者模式,策略模式,中介者模式等。

2 软件设计原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据6个原则来开发程序,从而提高软件开发效率,节约开发成本和维护成本。

注:这是尽可能遵循,不能为了遵循而故意使得代码凌乱与难懂,而且这些原则并不是一定要兼容和遵循。

2.1 开闭原则

开闭原则:对扩展开发,对修改关闭。指在程序中,需要扩展的时候,不能修改原来的代码,只能通过扩展去完成需要的业务。简单来说就是:代码需要添加功能,不能修改原来已有代码(但我们引用别人jar包时,我们根本就不能修改里面的代码)。

解决方案:使用接口和抽象类。 因为抽象类灵活性好,适应性广,只要抽象合理,就可以保证原来代码不改变,而细节可以由实现类来决定。

例子:游戏车中的皮肤设置。
分析:当我们打开一款赛车游戏,总可以让我们切换皮肤,甚至创建自己的皮肤。初始的时候,一般都会由一个默认皮肤,当自己完成任务获得新皮肤时,就可以修改成自己的皮肤。这些皮肤都有一个共同点,就是他们都是继承一个抽象类,然后每个具体的皮肤是抽象类子类,这样就可以添加新皮肤的时候,不需要去修改车的代码。
在这里插入图片描述
从上面的类图可以看出来,Car类中有一个抽象类属性skin,它是AbstractSkin的实现子类。所有当是默认的时候,使用的是DefaultSkin类,而当我们使用宝马皮肤是,只需要将BBWSkin皮肤注入给Car。如果需要自定义皮肤,我们只需要定义一个类是实现抽象类AbstractSkin就可以创建自己的皮肤,然后注入到Car中就可以了。这样就可以在不修改Car的代码从而是实现扩展。
代码例子:
Car类

//车类
public class Car {

    //抽象类
    private AbstractSkin skin;

    //用于皮肤的注入
    public void setSkin(AbstractSkin skin){
        this.skin = skin;
    }
    //皮肤展示方法
    public void display(){
        //调用皮肤展示
        skin.display();
    }
}

定义好Car类后,定义抽象皮肤类以及子类。
抽象类

/**
 * @version v1
 * @ClassName: AbstractSkin
 * @Author: ludehu
 * @Description: 抽象皮肤类
 */
public abstract class AbstractSkin {
    //显示的函数  抽象类
    public abstract void display();
}

默认皮肤子类实现

//默认皮肤
public class DefaultSkin extends AbstractSkin {
    public void display() {
        System.out.println("默认皮肤");
    }
}

宝马皮肤类(实现父类)

//宝马皮肤类
public class BBWSkin extends  AbstractSkin {
    public void display() {
        
        System.out.println("宝马皮肤");
    }
}

这样就完成了结构,那具体为什么这样就可以在不修改代码上扩展功能呢,我们来看测试类

public class Client {


    public static void main(String[] args) {
        //创建车对象
        Car car = new Car();
        //创建皮肤对象(默认和宝马皮肤)
        DefaultSkin defaultSkin = new DefaultSkin();
        BBWSkin bbwSkin = new BBWSkin();
        //注入
        car.setSkin(bbwSkin);
        //调用方法
        car.display();
    }
}

可以看到,我们使用皮肤是通过setSkin注入,当我们自己定义新皮肤时,不需要修改Car的代码,只需要创建一个mySkin实现抽象父类AbstractSkin,然后注入给Car就可以了,这就实现了开闭原则,对扩展开发,对修改关闭。

2.2 里氏代换原则

里氏代换原则:任何基类出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能修改父类原有的功能,就是指在子类继承父类的时候,除了新添加的功能外,尽量不要重写父类的方法。因为如果我们重写了父类的原本的方法,就很容易在某些调用该方法的地方出现错误,而且这种错误我们不易察觉,不便于维护。

例子:里氏代换原则最经典的例子:正方形例子。
分析:按数学角度想,我们可以知道,正方形是一个特殊的长方形,那么理所当然,正方形可以继承与长方形,但是因为正方形的特殊之处,需要重写父类的一些方法,才能表现长和宽相等。
代码:
长方形类:

/**
 * 长方形类
 */
public class Rectangle {
    private double length;
    private double width;

    public double getLength() {
        return length;
    }
    public void setLength(double length) {
        this.length = length;
    }
    public double getWidth() {
        return width;
    }
    public void setWidth(double width) {
        this.width = width;
    }
}

上面定义了两个属性,长和宽以及对应的get,set方法。
正方形:

/**
 * 正方形类
 */
public class Square extends Rectangle {

    @Override
    public void setLength(double length) {
        super.setLength(length);
        super.setWidth(length);
    }

    @Override
    public void setWidth(double width) {
        super.setWidth(width);
        super.setLength(width);
    }
}

这里重写了set宽和set长的方法,当set任何一个时候,都要调用父类的set方法确保长和宽相等。
我们定义了一个测试类,类中由一个方法resize(),用来扩宽图型:当图形为长方形的时候,将宽增大到长的+1大。当为正方形,我们希望并不做改变,但是结果如何呢?我们不妨试一下:
测试类:

package com.itludehu.principles.demo2.before;

public class RectangleDemo {

    public static void main(String[] args) {

        //创建长方形对象
        Rectangle rectangle = new Rectangle();
        //设置长和宽
        rectangle.setWidth(10);
        rectangle.setLength(20);
        //扩宽
        resize(rectangle);
        print(rectangle);
        System.out.print("----------------");
        //创建正方形,设置边长为20
        Square square = new Square();
        square.setLength(20);
        //扩宽
        resize(square);
        print(square);

    }

    //扩宽方法,将宽增大到比长大1
    public static void resize(Rectangle rectangle){
        while (rectangle.getWidth() <= rectangle.getLength()){
            rectangle.setWidth(rectangle.getWidth() + 1);
        }
    }

    //输出长和宽
    public static void print(Rectangle rectangle){
        System.out.println(rectangle.getLength());
        System.out.println(rectangle.getWidth());
    }
}

运行结果如下:
在这里插入图片描述
不难看到,程序陷入了死循环,因为正方形根本不能这样操作。
回顾里氏代换原则:基类出现的地方子类都可以替换,但是在上面的例子中,长方形出现的地方正方形并不能进行替换,因为程序会出现问题,这就是违背了里氏代换原则。
我学的时候会有这样疑惑:那既然我们知道扩宽的方法不能调用正方形,那我们就不调用就好了。但事实上,因为正方形是我们定义的,我们当然知道不能调用,但实际上程序员大多数都是使用别人的jar来简便开发,根本不可能对每个类和接口都了解,就会出现这样的bug,而往往这种bug是难以发现的。所有里氏代换原则也是一个很重要的设计原则。

2.3 依赖倒转原则

依赖倒转原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖于细节,细节应该依赖抽象。简单来说就是要求对抽象进行编程,不要对实现进行编程,这样就减低了客户与实现模块之间的耦合。
可以这样理解:当高模块组合依赖了低模块(应为需要调用低模块),于是我们常用的方法就是将低模块注入成员给高模块,方便调用。但是我们不能直接依赖实现类,要依赖于抽象类,这样具体的实现就可以在实例化后注入进去。用例子的话就是:我的电脑机箱不能将I7处理器固定死在里面,而是固定一个CPU接口,这样我想换I9处理器时,可以将i7换下来,装i9。那这个CPU接口就是抽象,也就是依赖于抽象,便于我们后面需要的更改。

2.4 接口隔离原则

接口隔离原则:客户端不应该依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。

例子:如果有一个类包含两个方法:A和B。现在有一个类C需要A这个方法,我们能想到的是,将C去继承这个类,但是问题来了,C其实不需要B这个方法,但是由于继承,C其实以及依赖于B这个方法了,所以走继承的方法是不行的。
解决办法:功能拆分。将这个类的A和B方法各自写一个接口,然后将C实现A这个接口,达到最小依赖上。以后如果需要B的功能,可以使用多实现,也实现B的接口就可以了。总的来说,就是面向接口,使用多实现的解决方案。

2.5 迪米特法则

迪米特法则又叫知识最少原则(只和你的朋友交谈,不要和陌生人说话)。含义是:如果两个软件实体无须直接通信,那就不应该发生直接相互调用,应该通过第三方转发调用。
朋友定义:存在关联,聚合组合关系的两个对象。

例子:住房子,我们一般可以找个中介,而不需要直接找房主。这就是通过第三方来转发消息,这样的话可以减低模块之间的耦合度,更便于代码的维护性和可复用性。可以通过中间件来理解迪米特法则。

2.6 合成复用原则

合成复用原则:尽量优先使用组合或者聚合等关联实现,其次再考虑使用继承来实现。通常类的复用指继承复用和合成复用。
优点:合成复用维持了类的封装性,我们不必知道类的具体内部实现细节。对象之间的耦合度减低,可以在类的位置声音抽象,等到具体实现后再进行注入。复用的灵活度高,可以在运行是进行。

3 设计模式之创建者模式

在学习了设计原则之后,我们清楚的了解设计需要遵循的一些规定,而设计模式就是这些原则的最好实践。这一小节介绍创建者模式。
创建者模式主要关注是:怎么样创建一个对象?在学习Java时,我们就知道对象通过new产生,但是实际上,我们并不经常使用这样的方法,而是将对象的创建与使用分开。这样减低了系统的耦合度,我们不在需要关注对象的创建细节。
创建者模式一共五种,分别是:单例模式, 工厂方法模式, 抽象工厂模式, 原型模式, 建造者模式。

3.1 单例设计模式

单例模式是java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种唯一访问其唯一对象的方法,可以之间访问,不需要实例化改对象。

单例模式分两种:饿汉式,懒汉式。简单来说,饿汉式就是在类加载就创建好所有的对象并初始化,而懒汉式是在等需要用的时候才实例化需要的对象。

1,饿汉式–方法一:静态成员变量

/**
 * 饿汉式  静态成员变量的方式
 */
public class Singleton {

    //私有构造方法
    private Singleton() {};

    //在本类中创建本类对象
    private static Singleton instance = new Singleton();

    //提供一个公共的访问方式获取该对象
    public static Singleton getInstance(){
        return instance;
    }
}

私有构造方法确保外界无法创建该对象,而getInstance方法提供一一个访问本类对象的方法。而静态成员就是创造对象使用。这样出现两个问题:对象实例化会占用内存;线程不安全。

2,饿汉式–方法二:静态代码块

/**
 * 饿汉式 -- 静态代码块的方式
 */
public class Singleton {

    //私有构造方法
    private Singleton() {};

    //声明singleton类型的变量
    private static Singleton instance;

    //在静态代码块进行赋值
    static {
        instance = new Singleton();
    }

    //对外提供获取该类的方法
    public static Singleton getInstance(){
        return instance;
    }
}

这种方法只是将创建对象放在静态代码块的位置。根据java编译的过程,我们知道静态代码会执行并且创建实例,这样方式比上一种方式优点是可以对初始化对象进行一些配置,也就是我们可以在静态代码块进行别的操作。但是同样有上面两种缺点:线程不安全以及内存占用。

3,懒汉式–方式一线程不安全

/**
 * 懒汉式---线程不安全
 */
public class Singleton {
    
    //私有构造方法
    private Singleton(){};
    //静态私有成员
    private static Singleton instance;  //仅仅声明,没有实例化
    
    //访问方法
    public static Singleton getInstance(){
        //进行判断,如果为null,才进行初始化 否则之间返回
        if(instance == null) {
            //首次调用,进行初始化
            instance = new Singleton();
        }
        return instance;
    }
}

在getInstance中进行实例化,可以满足当使用才进行实例化,这样解决了内存占用的问题,但很明显,这样依旧线程不安全。所有出现了第二种方式

4,懒汉式–方式二;线程安全(双重检测锁)

/**
 * 懒汉式---双重检查锁
 */
public class Singleton {
    
    //私有构造方法
    private Singleton(){}
    //静态成员对象
    private static volatile Singleton instance;
    
    //访问接口
    public static Singleton getInstance(){
        //进行判断是否为null
        if(instance == null){
            synchronized (Singleton.class){
                //第二次判断,如果为null 再实例化
                if(instance == null){
                    //实例化对象
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

我们在判断instance == Null 后使用锁再进行一次判断,并且再成员对象加入了volatile修饰。这是比较好的实现方式。

5,懒汉式–方法3:静态内部类
由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用的时候才会被加载,因此我们可以使用静态内部类的方法实现懒汉式加载单例模式。

/**
 * 懒汉模式  静态内部类
 */
public class Singleton {

    private Singleton() {};

    //定义一个静态内部类
    private static class SingletonHolder {
        //在内部类声明并初始化外部类的对象
        private static final Singleton instance= new Singleton();
    }

    //提供公共的访问对象
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

6,枚举方法
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分利用了枚举这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例中唯一不会被破坏的单例实现模式。

/**
 * 枚举实现方式
 */
public enum Singleton {

    INSTANCE;
}

//获取方法
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;
        System.out.println(instance == instance1);
    }

3.2 工厂方法模式

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

工厂方法模式主要角色:
*抽象工厂:提供创建产品的接口,调用者通过它访问具体工厂的工厂方法实例化产品。
*具体工厂:主要是实现抽象工厂中的抽象方法,完成其具体产品的创建。
*抽象产品:定义了产品的规范,描述产品的通用特性和功能。
*具体产品:实现了抽象产品角色定义的接口,有具体工厂方法创建。

案例描述:一个咖啡店可以进行咖啡的售卖,但咖啡店并不直接生产产品,而是向工厂申请,然后获得不同的咖啡产品。按照工厂方法:有一个抽象工厂定义生产咖啡的接口。很多个具体工厂实现抽象工厂。定义一个抽象产品类。很多和实现产品的具体类。
类图如下:
在这里插入图片描述
类图显示,定义了一个CoffeeFactory接口工厂,然后有具体两个实现:AmericanCoffeeFactory和LatterCoffeeFactory两个具体工厂,这样生产咖啡我们就可以调用这两个具体的工厂进行生产,但需要添加新的咖啡时,我们就只需要创建一个新的咖啡工厂实现抽象工厂和咖啡类。就可以生产新的咖啡了,咖啡店和工厂得到了解耦。
接口方法工厂:

/**
 * 工厂方法实现
 */
public interface CoffeeFactory {

    //创建一个咖啡的方法
    public Coffee createCoffee();
}

两个具体的工厂

/**
 * 美式咖啡工厂,实现了抽象工厂
 */
public class AmericanCoffeeFactory implements CoffeeFactory {
    @Override
    public Coffee createCoffee() {
        AmericanCoffee americanCoffee = new AmericanCoffee();
        return americanCoffee;
    }
}
/**
 * 拿铁咖啡工厂
 */
public class LatteCoffeeFactory implements CoffeeFactory {
    @Override
    public Coffee createCoffee() {
        return new LatterCoffee();
    }
}


咖啡店

/**
 * 咖啡店
 */
public class CoffeeStore {

    //声明一个工厂
    private CoffeeFactory coffeeFactory;

    public void setCoffeeFactory(CoffeeFactory coffeeFactory) {
        this.coffeeFactory = coffeeFactory;
    }

    //点餐
    public Coffee orderCoffee() {
        Coffee coffee = coffeeFactory.createCoffee();
        //加配料
        coffee.addMilk();
        coffee.addSuger();
        return coffee;
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        //创建一个咖啡店
        CoffeeStore coffeeStore = new CoffeeStore();
        //创建工厂对象
        AmericanCoffeeFactory factory = new AmericanCoffeeFactory();
        LatteCoffeeFactory latteCoffeeFactory = new LatteCoffeeFactory();
        //设置
        coffeeStore.setCoffeeFactory(latteCoffeeFactory);
        //获取咖啡
        Coffee coffee = coffeeStore.orderCoffee();
        System.out.println("-------------------");
        System.out.println(coffee.getName());
    }
}

这样,就可以使用不同的工厂对象来产生不同的产品了。

工厂方法模式优点:
用户只需要知道具体工厂的名称就可以得到想要的产品,无须知道产品的具体创建过程。
在系统增加产品的时候只需要增加对应的具体产品类和具体工厂类,无须对原工厂进行任何修改,满足开闭原则。
缺点:
每增加一个新产品都要增加一个具体产品类和具体工厂类,增加了系统的复杂度。

3.3 抽象工厂模式

上一个工厂方法只考虑了同一种工厂,就是只生产咖啡。抽象工厂模式是基于多产品考虑的。
抽象工厂模式考虑多级别产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品家族。而抽象工厂就是这一种工厂的实现方式。

概念:是一种为访问类提供一个创建一组相关或或许依赖对象的接口,且访问类无须指定所要产品的具体实现类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一种产品,而抽象工厂模式可以生产多个等级的产品。

抽象工厂模式的主要角色如下:
*抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建不同等级的产品。
*具体工厂:主要实现抽象工厂中的多个抽象方法,完成具体产品的创建。
*抽象产品:定义了产品的规范,描述了产品的主要特性。抽象工厂有很多种抽象产品。
*具体产品:实现了抽象产品所定义的接口,有具体工厂来进行创建。

因为仅仅是在抽象工厂定义了多个方法生产产品,然后在具体实现工厂方法进行多个产品的创建,与上一个例子类似,就不放出代码了。

抽象工厂模式优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户得到的始终时同一个产品中的产品。
抽象工厂模式缺点:当产品族中增加一个产品时,所有的工厂都需要进行更改。

3.4 原型模式

原型模式:用一个已经创建了实体作为原型,通过复制该原型对象来创建一个和原型相同的新对象。
原型模式包含下面角色:
*抽象原型类:规定了具体原型对象必须实现的Clone()方法。
*具体原型类:实现抽象原型类的Clone()方法,它是可被复制的对象。
*访问类:使用具体原型类的Clone()方法复制新的对象。

原型模式分为两种:
浅克隆:创建一个新对象,新对象的属性和原来对象相同,对于非基本属性类型,任指向原有属性指向的对象的内存地址。
深克隆:创建一个对象,属性中引用的其他对象也会被克隆,不再指向原来对象的地址。

抽象原型类有java包提供的Cloneable 接口,只需要实现这个接口并重写clone方法即可。
具体原类型:

//去实现Cloneable这个接口
public class Realizetype implements Cloneable {

    public Realizetype() {
        System.out.println("具体的原型对象创建成功");
    }
    //修改为public 并返回原型类。
    @Override
    public Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型复制成功");

        return (Realizetype)super.clone();
    }
}

访问类:

//访问类
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        //创建一个原型对象
        Realizetype realizetype = new Realizetype();

        //调用Clone进行克隆
        Realizetype clone = realizetype.clone();

        System.out.println("原型对象与克隆出来的对象是否是同一个:"+realizetype.equals(clone));
    }
}

在访问类中,我们先创建了一个原型类,并通过原型类的clone方法克隆出来一个新对象,并打印看是否两个对象是同一个地址。
在这里插入图片描述
可以看出,并不是同一个对象,说明使用clone方法是新创建了一个对象出来,而且没有通过无参构造方法创建。

原型模式的例子:三好学生奖状。定义一个奖状类实现Cloneable接口,并添加一个成员变量name。然后重写clone方法,参数为name。只要在clone时候用名字作为参数,这样就可以克隆一堆名字不一样的奖状出来了。

使用场景:对象的创建比较复杂,可以使用原型模式创建对象。性能和安全性比较高。

3.5 建造者模式

建造者模式:将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构造出复杂的对象。这个模式可以用于:某个对象的构造过程复杂的情况。

建造者模式包含如下角色:
*抽象建造者:这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
*具体建造者类:实现抽象建造者接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
*产品类:要创建的复杂对象。
*指挥者类:调用具体创建者来创建复杂对象的各个部分,在指挥者中不涉及具体产品的信息,只保证对象各部分完整的创建或按照某种顺序创建。

例子:创建共享单车。
分析:生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。对于自行车的生产就可以使用建造者模式。

具体的代码如下:

//具体的产品对象 bike
public class Bike {

    private String frame; //车架
    private String seat;  //车座

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }

    public String getSeat() {
        return seat;
    }

    public void setSeat(String seat) {
        this.seat = seat;
    }
}
//抽象类builder
public abstract class Builder {

    //声明bike类型的变量,并进行赋值
    protected Bike bike = new Bike();
    //抽象方法,构建组件
    public abstract void buildFrame();
    public abstract void buildSeat();

    //构建自行车方法
    public abstract Bike createBike();
}
//具体的构建者,用来构建MoBile单车
public class MobileBuilder extends Builder {

    @Override
    public void buildFrame() {
        bike.setFrame("碳钎维车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("真皮车座");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}
//具体的构建者,用来构建Ofo单车
public class OfoBuilder extends Builder {
    @Override
    public void buildFrame() {
        bike.setFrame("铝合金车架");
    }

    @Override
    public void buildSeat() {
        bike.setSeat("橡胶车座");
    }

    @Override
    public Bike createBike() {
        return bike;
    }
}
//指挥者类
public class Director {

    //声明一个builder类型的变量
    private Builder builder;

    public Director(Builder builder){
        this.builder = builder;
    }

    //组装自行车
    public Bike construct() {
        //组装顺序
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}
//测试类
public class Client {

    public static void main(String[] args) {
        //创建指挥者对象
        Director director = new Director(new MobileBuilder());
        //让指挥者组装这自行车
        Bike construct = director.construct();

        System.out.println(construct.getFrame());
        System.out.println(construct.getSeat());
    }
}

优缺点:
优点:建造者模式封装性很好。使用建造者可以有效的封装变化,在使用建造者模式的场景中,一般产品和建造者类是比较稳定的,因此,将主要的业务逻辑对封装在指挥者类中对整体而言可以取得比较好的稳定性。在建造者模式中,客户端不需要知道产品内部的组成细节。
缺点:建造者模式所创建的产品一般由较多的相同点,其组成部分相识,如果产品之间差异性很大,不建议使用建造者模式。

4 设计模式之结构型模式

结构型模式描述如何将类或者对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或者聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。
结构型分以下7中:
*代理模式
*适配器模式
*装饰者模式
*桥接模式
*外观模式
*组合模式
*享元模式

4.1 代理模式

代理模式:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不合适或者不可以之间引用目标对象,代理对象作为访问对象和目标对象之间的中介。代理模式根据代理类生成时间分为两种:静态代理模式,动态代理模式。

代理模式分为三种角色:
*抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
*真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是目标对象。
*代理类:提供了与真实主题相同的接口,其内部含由对真实主题的引用,它可以访问,控制或扩展真实主题的功能。

静态代理模式案例:
如果要买火车票的话,需要去火车票买票,坐到火车站,排队等一系列操作,显然比较麻烦。而火车站在多个地方都有代理点,也就是代售点,我们去代售点购买就比较方便了。这个例子就是典型的代理模式,火车站是目标对象,代售点就是代理对象。
代码如下:

//抽象主题类,定义一些行为
public interface SellTickets {
    public void sell();
}
//火车站,目标对象,实现抽象代理类
public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站卖票!");
    }
}
//代理点
public class ProxyPoint implements SellTickets{

    //静态成员
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("代理处收取代理费用");
        trainStation.sell();
    }
}
//测试类
public class Client {
    public static void main(String[] args) {
        //代理类
        ProxyPoint proxyPoint = new ProxyPoint();
        //进行购买
        proxyPoint.sell();
    }
}

运行图:
在这里插入图片描述
从上面的代码可以看得出测试类访问的是proxyPoint对象,也就是中介。

4.2 适配器模式

适配器模式:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。好的理解就是耳机适配器,我们耳机有两种接口,一种圆孔的,而一种充电器口的,使用一个适配器就可以将圆孔耳机也插入充电口。

适配器模式分为类适配器和对象适配器模式,前者类之间的耦合度比较高,而且要求程序员了解所有组件库中的内部结构,所有使用少一些,我们主要使用对象适配器模式。

适配器模式包含以下主要角色:
*目标接口:当前系统业务所期待的接口,它可以是抽象类或者接口。
*适配者 类:它是被访问和适配器的现存组件库中的组件接口。
*适配器类:它是一个转换器,通过继承或者引用适配者的对象,把适配者的接口转换成目标接口,让客户按目标接口的格式访问适配者。

这里就仅举例对象适配器模式。
实现方法:对象适配器模式可采用将现有的组件库中已实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
例子:读卡器。

代码例子:

//目标接口
public interface SDCard {

    //从SD卡中读取数据
    String readSD();
    //对SD卡中写入数据
    void writeSD(String message);
}
//具体的SD卡类
public class SDCardImpl implements SDCard {
    @Override
    public String readSD() {
        return "SDCard read msg: hello world SD";
    }

    @Override
    public void writeSD(String message) {
        System.out.println("SDCard write msg: "+message);
    }
}
//适配者类接口
public interface TFCard {

    //从TF卡中读数据
    String readTF();
    //玩TF卡中写数据
    void writeTF(String message);
}
//适配者类
public class TFCardImpl implements TFCard {
    @Override
    public String readTF() {
        String message = "TFCard read msg: hello world TF";
        return message;
    }

    @Override
    public void writeTF(String message) {
        System.out.println("TFCard write msg: "+message);
    }
}
//计算机类
public class Computer {

    //从SD卡中读取数据
    public String readSD(SDCard sdCard){
        if(sdCard == null){
            throw new NullPointerException("sd card is not null");
        }
        return sdCard.readSD();
    }
}
//适配器类
public class SDAdapterTF implements SDCard {

    //聚合适配者
    private TFCard tfCard;

    //进行有参构造方法
    public SDAdapterTF(TFCard tfCard){
        this.tfCard = tfCard;
    }

    @Override
    public String readSD() {
        System.out.println("adapter read it card");
        return tfCard.readTF();
    }

    @Override
    public void writeSD(String message) {
        System.out.println("adapter write it card");
        tfCard.writeTF(message);
    }
}
//测试类
public class Client {

    public static void main(String[] args) {
        //创建计算机类
        Computer computer = new Computer();
        //读取SD卡中的数据
        String message = computer.readSD(new SDCardImpl());
        System.out.println(message);

        System.out.println("======================>>");
        //使用TFCard读书数据

        //创建适配器对象
        SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImpl());
        String msg = computer.readSD(sdAdapterTF);
        System.out.println(msg);
    }

}

应用场景:
以前开发的系统满足新系统功能需要的类,但其接口同新系统的接口不一致。使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

4.3 装饰者模式

装饰者模式:指在不改变现有对象结构的情况下,动态的给该对象增加一些职能的模式。

装饰者模式中的角色:
*抽象构件角色:定义一个抽象接口以规范准备接收附加责任的对象。
*具体构件角色:实现抽象构件,通过装饰者角色为它添加一些职责。
*抽象装饰者角色:继承或者实现抽象构件,可以通过其子类扩展具体构件的功能。
*具体装饰者角色:实现抽象装饰者类的相关方法,并给具体构件对象添加附加的职责。

例子:购车
分析:我们在买车的时候,一般会有一个标配(不加其他配件),但是但我们需要添加配件如:GPS,氙气灯时就需要重新计算金额。这样我们可以将GPS,氙气灯等为具体的构件角色,而车是具体装饰者角色,也就是被装饰的对象。
代码:

//抽象装饰者
public abstract class Component {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public abstract double getPrice();
	
	public void bid(){
		System.out.println(this.getName() + this.getPrice());
	}
}

//具体的装饰者
public class MainBody extends Component{
	public MainBody() {
		this.setName("标配");
	}

	@Override
	public double getPrice() {
		return 100000;
	}

}

//抽象组件类
public abstract class Decorator extends Component {
	public abstract String getName();
}//具体组件类 实现抽象组件类
public class GPS extends Decorator{
	private Component comp;
	public GPS(Component comp) {
		this.comp = comp;
	}

	@Override
	public String getName() {
		return comp.getName()+ " 导航仪";
	}

	@Override
	public double getPrice() {
		return comp.getPrice() + 8000;
	}

}
public class Lamp extends Decorator{
	private Component comp;
	public Lamp(Component comp) {
		this.comp = comp;
	}

	@Override
	public String getName() {
		return comp.getName()+ " 氙气灯";
	}

	@Override
	public double getPrice() {
		return comp.getPrice() + 5000;
	}

}
//测试类
public class Client {
	public static void main(String[] args) throws IOException {
		Component mainBody = new GPS(new Lamp(new MainBody()));
		mainBody.bid();
		
	}
}

运行如下:
在这里插入图片描述
测试类可以看出,我们在添加组件的时候只用将组件new出来,并添加进去,就可以自己计算总的花费金额。

4.4 桥接模式

问题概述:现在有一个需求,需要创建不同的图形,每个图形有不同的颜色。如果按照类的继承来设计的话,我们会有一个父类图形类,然后又几个子类:圆形,长方形,正方形,而每个图形又三种颜色,那又再继承三个子类为:绿色圆形,红色圆形,。这样设计的话,就有3*3=9个类,会出现类爆炸问题,这样显然是不行的。而桥接模式就是解决这样的问题。

桥接模式:将抽象与实现分离,使他们可以独立变化。他们是用组合关系代替继承关系来实现,从而减低了抽象和实现这两个可变维度的耦合度。

在上面的例子中,其实就可以分为两个维度,一个是图形,一个是颜色。

桥接模式包含以下主要角色:
*抽象化角色:定义抽象类,并包含一个对实现化对象的引用。
*扩展抽象化角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法
*实现化角色:定义实现化角色的接口,供扩展抽象化角色调用。
*具体实现化角色:给出实现化角色接口的具体实现。

4.5 外观模式

外观模式:又名门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统容易被访问的模式。该模式对外有一个统一的接口,外部应用不关心内部之系统的具体细节,这样就会大大减低应用程序的复杂度,提高了程序的可维护性。通俗来说,就是现在手机的充电口和耳机孔,手机只用暴露一个接口,而充电和耳机都是这一个接口,这就是外观模式。这种模式使得系统方便维护。

外观模式包含以下主要角色:
*外观角色:为多个子系统对外提供一个共同的接口。
*子系统角色:实现系统的部分功能,客户可以通过外观角色访问它。

外观模式是“迪米特法则”的典型应用。

例子:车子一键启动。
分析:我们在开启汽车时,都是有一个一键启动。当按下按钮时,汽车会启动电源,打开引擎,打开GPS。在这里,一键启动就是外观模式的外观角色,它提供了子系统:启动电源,发动引擎,打开GPS这些子系统的操作,而不需要我们一个一个进行访问操作。说白了,外观模式就是将一系列子系统包装起来,使用一键启动,调用者并不关心内部之间调用,也就是我们不关心是先发动引擎还是先打开GPS,我们只需要一键启动就行。
而外观模式的实现方法就是将子系统聚合起来,然后再内部实现他们之间的顺序运行,而对外就提供一个接口(一键启动接口)。

代码:

//具体的子系统角色类--电源启动
public class ElectronicDevice {
	public void turnOn(){
		System.out.println("turn on the electronic devices");
	}
	
	public void turnOff(){
		System.out.println("turn off the electronic devices");
	}
}

//具体的子系统角色类--引擎
public class Engine {
	public void start(){
		System.out.println("start the engine");
	}
	
	public void stop(){
		System.out.println("stop the engine");
	}
}

//具体的子系统角色类--GPS
public class GPS {
	public void connect(){
		System.out.println("connect the satellite");
	}
	
	public void disconnect(){
		System.out.println("disconnect the satellite");
	}
}

//外观角色 聚合了子系统的角色
public class ControlSystem {
	Engine engine = new Engine();
	GPS gps = new GPS();
	ElectronicDevice electronicDevice = new ElectronicDevice();

	//通过一键功能,完成所有的任务
	public void powerOn(){
		engine.start();
		gps.connect();
		electronicDevice.turnOn();
	}
	
	public void powerOff(){
		engine.stop();
		gps.disconnect();
		electronicDevice.turnOff();
	}
}
//测试类
public class Client {
	public static void main(String[] args) {
		ControlSystem cs  = new ControlSystem();
		cs.powerOff();
	}

}

代码可以看出,在外观类中聚合了子系统,然后提供一个一键启动的功能,顺序的将子系统一起启动起来。

好处:
减低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
对客户屏蔽了子系统的组件,减少了客户处理对象的数目,使得子系统使用起来更加方便。
缺点:
不符合开闭原则,修改比较麻烦。

4.6 组合模式

组合模式:又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形接口来组合对象,用来表示部分以及整体层次,这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。比如我们操作系统中的文件系统:文件加里面有文件以及小的文件夹。这就是一个部分整体的组合模式。我们可以对文件进行删除,也可以对文件夹进行删除,就是类成员定义了自己。

组合模式主要包含了以下主要角色:
*抽象根节点:定义系统个层次对象共有方法和属性,可以预先定义一些默认行为和属性。
*树枝节点:定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
*叶子节点:叶子节点对象,其他再无分支,是系统层次遍历的最小单位。

例子:公司层级模式。
分析:有一个公司在北京有总公司,总公司里面有人事部,财务部。而该公司在南京以及上海都有分公司,分公司里面也有人事部和财务部。这就是一个部分组合的模式。在上面的例子中,公司就是树枝节点,而部门就是叶子节点。可以在公司中聚合公司,就可以完成这样的结构。

代码如下:

//抽象公司类,定义了一些公司需要的规范
public abstract class Organization {
	private String name;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public abstract void display(int depth);
	
	public abstract void add(Organization org);
	
	public abstract void remove(Organization org);
}

//公司类 树枝节点
public class Company extends Organization{
	List<Organization> list = new ArrayList<Organization>();

	public Company(String name) {
		super.setName(name);
	}

	@Override
	public void add(Organization org){
		list.add(org);
	}

	@Override
	public void remove(Organization  org){
		list.remove(org);
	}

	@Override
	public void display(int depth) {
		String temp = "";
		for (int i = 0; i < depth; i++) {
			temp += "-";
		}
		System.out.println(temp + this.getName());
		for (int i = 0; i < list.size(); i++) {
			list.get(i).display(depth + 2);
		}
	}
}

//叶子节点--部门类
//财务部  
public class FinacialDepartment extends Organization{
	public FinacialDepartment(String name) {
		super.setName(name);
	}
	
	@Override
	public void display(int depth) {	
		String temp = "";
		for (int i = 0; i < depth; i++) {
			temp += "-";
		}
		System.out.println(temp + this.getName());
	}

	@Override
	public void add(Organization org) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void remove(Organization org) {
		// TODO Auto-generated method stub
		
	}

}
//人事部
public class HRDepartment extends Organization{
	public HRDepartment(String name) {
		super.setName(name);
	}
	
	@Override
	public void display(int depth) {	
		String temp = "";
		for (int i = 0; i < depth; i++) {
			temp += "-";
		}
		System.out.println(temp + this.getName());
	}

	@Override
	public void add(Organization org) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void remove(Organization org) {
		// TODO Auto-generated method stub
		
	}

}
//测试类
public class Client {
	public static void main(String[] args) {
		Organization beijingCom = new Company("北京总公司");
		Organization nanjingCom = new Company("南京分公司");
		Organization shanghaiCom = new Company("上海分公司");
		Organization beijingHDep = new HRDepartment("北京人事部");
		Organization nanjingHDep = new HRDepartment("南京分公司人事部");
		Organization nanjingFDep = new FinacialDepartment("南京分公司财务部");
		Organization shahaiFDep = new FinacialDepartment("上海分公司财务部");
		
		//建立联系
		beijingCom.add(beijingHDep);
		nanjingCom.add(nanjingHDep);
		nanjingCom.add(nanjingFDep);
		shanghaiCom.add(shahaiFDep);
		beijingCom.add(shanghaiCom);
		beijingCom.add(nanjingCom);
		//输入公司层级
		beijingCom.display(0);
	}
}

运行测试类,得到输出;
在这里插入图片描述
代码上可以看出,公司类聚合了自己,将自己当成了自己的属性,然后叶子成员都是抽象类的实现,就可以进行成员的注入,这就是实现了部分整体模式,也就是组合模式。

组合模式优点:
组合模式可以清楚的定义分层次的复杂对象,表示对象的全部或者部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
在组合模式中,新增叶子节点很方便,不需要修改代码,符合开闭原则。

4.7 享元模式

享元模式:运用共享技术来有效支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量,避免大量相似对象的开销,从而提高系统资源的利用率。
例子:共享单车。并不需要每个人有一个单车,而是使用共享的单车,这样就大大节省系统中的同一种对象。实例:各种连接池。

享元模式中存在以下两种状态:
*内部状态,即不会随着环境改变而改变的可共享部分。
*外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部坏。
享元模式主要有以下角色:
*抽象享元角色:通常是一个接口或者抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据,同时通过这些方法设置外部数据。
*具体享元角色:它实现了抽象享元角色,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常设计成单例模式,为每一个具体的享元类提供唯一的享元对象。
*非享元角色:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类设计为非共享具体享元类;当需要一个的时候直接实例化一个对象出来即可。
*享元工厂角色:负责创建和管理享元角色,当客户对象要求一个享元对象时,工厂检查自己时候拥有,有即放回,没有需要创建一个并保存再返回。

例子:俄罗斯方块
分析:俄罗斯方块中有五种模式,即L型,I型等,但是数量很多,如果我们为每一个形状都要创建的化,会占很多内存,于是我们设计一个工厂来保存每个形状一个实例。当需要的时候,只需要在工厂中获取即可,这样大大节省了内存空间。

代码如下:

//抽象享元角色
public abstract class AbstractBox {

    //获取图形的方法
    public abstract String getShape();

    //显示图形以及颜色
    public void display(String color){
        System.out.println("形状:"+getShape()+" , 颜色: "+color);
    }
}

//具体的I图形类
public class IBox extends AbstractBox {
    @Override
    public String getShape() {
        return "I";
    }
}

//具体的L图形类
public class LBox extends AbstractBox {
    @Override
    public String getShape() {
        return "L";
    }
}
//具体的O图形类
public class OBox extends AbstractBox {
    @Override
    public String getShape() {
        return "O";
    }
}
//工厂类,将该类设计为单例
public class BoxFactory {

    //工厂里面的图形产品
    private HashMap<String,AbstractBox> hashMap;
    private static BoxFactory boxFactory;
    //私有构造函数,初始化成员
    private BoxFactory(){
        hashMap = new HashMap<String, AbstractBox>();
        hashMap.put("I",new IBox());
        hashMap.put("O",new OBox());
        hashMap.put("L",new LBox());
    }
    //根据名称获取图形对象,从hashMap中获取
    public AbstractBox getBox(String type){
        return hashMap.get(type);
    }

    //获得工厂实例 使用双重判断
    public static BoxFactory getInstance(){
        //第一次判断
        if(boxFactory == null){
            synchronized (BoxFactory.class){
                //第二次判断
                if (boxFactory == null){
                    boxFactory = new BoxFactory();
                }
            }
        }
        return boxFactory;
    }
    
}

//测试类
public class Client {

    public static void main(String[] args) {
        //获取I图形
        //先获取工厂对象
        BoxFactory instance = BoxFactory.getInstance();
        AbstractBox i = instance.getBox("I");
        i.display("绿色");
    }
}

享元模式的优缺点:
优点:极大减少内存中相似或者相同的数量对象,节约系统资源,提供系统性能。享元模式中外部状态相较独立,且不影响内部状态。
缺点:为了共享对象,需要将享元对象的状态外部化,分离内部状态和外部状态,相较复杂。

5 设计模式之行为型模式

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或者对象之间怎样相互协作共同完成单个对象都无法完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式和对象行为模式,前者采用继承机制在类间分派行为,后者采用组合或者聚合在对象之间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

行为型模式一共有11种,分别是:模板方法模式,策略模式,命令模式,责任链模式,状态模式,观察者模式,中介者模式,迭代器模式,访问者模式,备忘录模式,解释器模式。

5.1 模板方法模式

在面向对象程序设计的过程中,程序员一般会遇到一种情况:设计一个系统知道了算法的关键步骤,而且确定了这些步骤的执行顺序,但是某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境有关。这很像高中解数学题,一般都有一个步骤:先算X,再算Y,最后求得解。但是我们只是知道步骤顺序,具体的X,Y的值我们并不知道,于是就可以先定义一个模式方法,之后将数据带入模板即可。这就是模板方法解决的问题

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

模板方法模式包含以下主要角色:
*抽象类:负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
** 模板方法:定义了算法的框架,按某种顺序调用基本方法。
**基本方法:是实现算法步骤的方法,分为下面三种。
****抽象方法:由抽象类定义,具体子类实现。
****具体方法:由抽象类或者具体类声明并实现。子类进行覆盖或者继承。
****钩子方法:在抽象类以及实现,包括用于判断的逻辑方法和需要子类重写的空方法。
*具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

例子:炒菜。
分析:炒菜步骤是固定的,分为倒油,热油,放蔬菜,放调料品,翻炒等步骤。这些步骤可以写成模式,然后对不同的菜重写以下放蔬菜和放调料品的具体实现。

代码如下:

//抽象类,定义一些模板方法和基本方法
public abstract class AbstractClass {

    //基本方法
    //倒油
    public void pourOil(){
        System.out.println("倒油");
    }
    //热油
    public void heatOil(){
        System.out.println("热油");
    }
    //抽象方法  加蔬菜
    public abstract void pourVegetable();
    //抽象方法  加调理品
    public abstract void pourSauce();
    //翻炒
    public void fry(){
        System.out.println("翻炒");
    }

    //模板方法 步骤的框架和顺序
    public final void template(){
        //第一步:倒油
        this.pourOil();
        //第二步,热油
        this.heatOil();
        //第三步,加蔬菜
        this.pourVegetable();
        //第四步,加调料品
        this.pourSauce();
        //第五步,翻炒
        this.fry();
    }
}
//具体子类,炒鸡蛋 实现两个具体步骤
public class ConcreteClass_egg extends AbstractClass {


    @Override
    public void pourVegetable() {
        System.out.println("放入鸡蛋");
    }

    @Override
    public void pourSauce() {
        System.out.println("放入紫菜");
    }
}
//具体实现类--炒土豆
public class ConcreteClass_potato extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("放入土豆");
    }

    @Override
    public void pourSauce() {
        System.out.println("放入辣椒");
    }
}
//测试类
public class Client {

    public static void main(String[] args) {
        //创建炒鸡蛋类
        AbstractClass egg = new ConcreteClass_egg();
        egg.template();

        System.out.println("===============================");
        //创建炒土豆类
        AbstractClass potato = new ConcreteClass_potato();
        potato.template();
    }
}

运行如下:
在这里插入图片描述
从代码上看来,当我们想要炒白菜的时候,就只需要创建一个类继承抽象类,然后实现放白菜和放调料品这两个方法即可。因为他们步骤都是一致的,所以可以使用模板方法模式。

优点:提高了代码的复用性。实现了控制反转,符合开闭原则。
缺点:每个实现类都需要创建一个子类,类的个数会增加。需要通过子类来决定父类中某个步骤是否执行,提高了代码阅读的难度。

5.2 策略模式

策略模式:该模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

就比如我们出行,选择的交通工具就是一系列的算法,由骑车,步行,公交以及出租车,这些都是完成出行的算法。我们可以选择不同的算法而不会影响我们的出行,这就可以使用策略模式。

策略模式包含以下主要角色:
*抽象策略类:这是一个抽象角色,通常由一个接口或者抽象类实现。此角色给出所有的具体策略类所需要的接口。
*具体策略类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
*环境类:持有一个策略类的引用,最终给客户端使用。

例子:去火车站。
分析:去火车站可以有多种方式,每个方式就是一个策略。对方式进行抽象,形成一个抽象策略类,然后具体策略如:步行,公交实现抽象策略。然后再环境类聚合抽象策略类,当使用的时候,注入具体策略就可以实现。
代码如下:

//抽象策略类 顶一个出现方法
public interface AbstractMethod {

    public void method();
}
//具体策略A---步行
public class MethodA implements AbstractMethod {
    @Override
    public void method() {
        System.out.println("步行去火车站");
    }
}
//具体策略B---公交车
public class MethodB implements AbstractMethod {
    @Override
    public void method() {
        System.out.println("公交车去火车站");
    }
}
//环境类,聚合抽象策略---
public class Environment {

    //抽象策略
    private AbstractMethod abstractMethod;

    //构造方法
    public Environment(AbstractMethod abstractMethod) {
        this.abstractMethod = abstractMethod;
    }

    public void setAbstractMethod(AbstractMethod abstractMethod) {
        this.abstractMethod = abstractMethod;
    }

    //执行方法
    public void method(){
        //调用策略方法
        this.abstractMethod.method();
    }
}
//测试类
public class Client {

    public static void main(String[] args) {
        //创建一个环境类,注入策略A
        Environment environment = new Environment(new MethodA());
        environment.method();

        System.out.println("================");
        //注入策略B
        environment.setAbstractMethod(new MethodB());
        environment.method();
    }
}

优点:
策略之间可以自由替换。避免使用过多的if else语句。符合“开闭原则”。
缺点:客户端需要知道所有策略并自行选择。

5.3 命令模式

命令模式:将一个请求封装为一个对象,使得发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行储存,传递,转发,调用与管理。通俗的理解,就是动作的进度,有时候我们会有一个动作以及回退,就是回到动作发生之前,这样,动作就可以设计成命令,通过对命令进行管理就可以进行回档的操作。

命令模式包含以下主要角色:
*抽象命令类角色:定义命令的接口,声明执行的方法。
*具体命令角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者功能完成命令要执行的操作。
*实现者/接收者角色:接收者,真正执行命令的对象。
*请求者角色:要求命令对象执行请求。

由于命令需要进行管理,通常需要抽象一个命令类接口,然后将各种命令实现这个接口并聚合接收者执行命令。

优点:
减低系统的耦合度。增加和删除命令比较容易,满足开闭原则。
方便实现Undo和Redo操作,也就是命令的撤销与恢复。

5.4 责任链模式

责任链模式:又名职责链模式,为了避免请求发送者与多个请求处理中耦合在一起,将所有请求的处理者通过前一个对象记住下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,知道有对象处理它为止。

责任链模式包含以下主要角色:
*抽象处理者角色:定义一个处理的接口,包含抽象处理方法和一个后继连接。
*具体处理者角色:实现抽象处理者的处理方法,判断其是否能偶处理本次请求,如果可以,直接处理,否则将请求转发给它的后继者。
*客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

例子:请假系统。
分析:我们一般请求时,会根据请假的天数去找不同的负责人。比如,三天以内辅导员可以批准,但是三天以上七天以内,需要学院批准,而七天以上,需要学校批准。这就是一个责任链模式。在这里,辅导员----学院----学校就形成了一个责任链,请假者发出请求,交给这个责任链处理即可。

代码:

//请假的请求
public class LeaveRequest {

    //请假人
    private String name;
    //请假天数
    private Integer day;
    //构造函数
    public LeaveRequest(String name, Integer day) {
        this.name = name;
        this.day = day;
    }

    public String getName() {
        return name;
    }

    public Integer getDay() {
        return day;
    }
}
//抽象处理者
public abstract class Handler {

    //领导名称
    private String name;
    //该领导处理的请假区间
    private Integer numStart;
    private Integer numEnd;

    //声明下一级别处理者
    private Handler nestHandler;

    //设计下一个处理者对象
    public void setNestHandler(Handler nestHandler) {
        this.nestHandler = nestHandler;
    }

    //构造方法,给出领导级别,以及处理区间
    public Handler(String name, Integer numStart, Integer numEnd) {
        this.name = name;
        this.numStart = numStart;
        this.numEnd = numEnd;
    }

    //处理抽象方法 每个级别处理方式不同
    protected abstract void handlerLeave(LeaveRequest leaveRequest);
    //级别不够,提交给下一个处理者 主要责任传递方法
    public final void submit(LeaveRequest leaveRequest){
        //如果级别不够并且有下一处理者,发给下一处理者
        if(this.nestHandler != null && leaveRequest.getDay() > numEnd){
            //发给下一级
            this.nestHandler.submit(leaveRequest);
        } else {
            //领导处理
            this.handlerLeave(leaveRequest);
            System.out.println("流程结束");
        }
    }
}
//第一级别 辅导员类
public class Teacher  extends  Handler{

    //构造方法
    public Teacher() {
        super("辅导员", 0, 3);
    }

    //处理方法
    @Override
    protected void handlerLeave(LeaveRequest leaveRequest) {
        System.out.println(leaveRequest.getName()+"请假"+leaveRequest.getDay()+"天成功!"+"辅导员批准!");
    }
}
//第二级别 学院类
public class College extends  Handler{

    //构造方法
    public College() {
        super("学院", 3, 7);
    }

    //处理方法
    @Override
    protected void handlerLeave(LeaveRequest leaveRequest) {
        System.out.println(leaveRequest.getName()+"请假"+leaveRequest.getDay()+"天成功!"+"学院批准!");
    }
}
//第三级别 学校类
public class School extends  Handler{

    //构造方法
    public School() {
        super("学校", 7, 100);
    }

    //处理方法
    @Override
    protected void handlerLeave(LeaveRequest leaveRequest) {
        System.out.println(leaveRequest.getName()+"请假"+leaveRequest.getDay()+"天成功!"+"学院批准!");
    }
}
//测试类
public class Client {

    public static void main(String[] args) {
        //创建责任链
        School school = new School();
        College college = new College();
        Teacher teacher = new Teacher();
        //构造责任链
        teacher.setNestHandler(college);
        college.setNestHandler(school);

        //创建请假人
        LeaveRequest leaveRequest = new LeaveRequest("张山",11);
        //提交给辅导员解决
        teacher.submit(leaveRequest);
    }
}

优点:
*减低了对象之间的耦合度。增强了系统的可扩展性。增强了给对象指派职责的功能。每个类只需要处理自己的该处理的工作,不能处理的就传给下一个责任者,明确各类处理的业务即可。

5.5 状态模式

状态模式:对有状态的对象,把复杂的”判断逻辑“提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式包含以下主要角色:
*环境角色:也称上下文,它定义了客户程序所需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态来处理。
*抽象状态角色:定义一个接口,用以封装环境对象中特定状态对应的行为。
*具体状态角色:实现抽象状态所对应的行为。

优点:将所有与某个状态有关的行为放到一个类中,并且可以方便的新增一个状态,只需要改变对象状态即可该变对象的行为。允许状态转换逻辑与状态对象合成一体。
缺点:状态模式的使用会增加系统类和对象的个数。状态模式对”开闭原则“并不友好。

5.6 观察者模式

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

观察者模式包含以下主要角色:
*抽象主题:被观察者,抽象主题将所有的观察者保存到一个集合里,每个主题可以有任意数量的观察者,抽象主题是一个接口,可以增加和删除观察者。
*具体主题:该角色实现抽象主题,保存观察者。在具体主题发送动态改变时,会依次通知集合中的观察者。
*抽象观察者:是观察者的抽象类,它定义了一个跟新接口,使得在得到主题更改通知时更新自己。
*具体观察者:实现抽象观察者的方法,在得到主题后改变自己。

该模式使用的时聚合,在主题类聚合观察者集合,当发送状态改变时,依次通知聚合中的每一个观察者。

优点:
减低了目标与观察者之间的耦合关系,两者之间是抽象耦合。
被观察者发送通知,所有注册的观察者都会收到消息。
缺点:如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时。这个模式害怕循环调用,就是观察者是其他观察者的被观察者,主样通知会导致系统崩溃。

5.7----5.11 其他模式

后面几种模式并不常用,所以本文就不进行介绍,如读者有兴趣,可自行查看相关资料。
它们分别是:中介者模式; 迭代器模式; 访问者模式; 解释器模式; 备忘录模式。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值