设计模式

设计模式

目录

  • 设计模式
    • 创建型模式
      • 单例模式
        • 1.恶汉式
        • 2.懒加载(双重锁)
        • 3.懒加载(内部静态类)
        • 4.枚举单例
        • 反射破解单例模式
        • 反序列化破解单例模式
      • 工厂模式
        • 1.简单工厂
        • 2.抽象工厂
      • 建造者模式
        • 介绍
        • 代码
      • 原型模式
        • 介绍
        • 浅克隆
        • 深克隆(通过Cloneable)
        • 深克隆(通过序列化)
    • 结构型模式
      • 适配器模式(adapter)
        • 介绍
      • 代理模式(proxy)*
        • 介绍
        • 静态代理
        • 动态代理
      • 桥接模式(bridge)
        • 介绍
      • 组合模式(composite)
        • 介绍
      • 装饰器模式(decorator或wrapper)
        • 介绍
        • 代码
      • 外观模式(facade)
        • 介绍
      • 享元模式(flyweight)
        • 介绍
        • 代码
    • 行为型模式
      • 责任链模式(chain of responsibility)
        • 介绍
        • 代码
      • 迭代器模式(iterator)
        • 介绍
      • 中介者模式
        • 介绍
        • 代码
      • 策略模式(strategy)
        • 介绍
      • 模板方法模式(template method)
        • 介绍
        • 代码
      • 状态模式
        • 介绍
      • 代码
      • 观察者模式
        • 介绍
      • 代码1
      • 代码2(使用jdk提供的观者接口)
      • 备忘录模式
        • 介绍
        • 代码
  • 总结

  • 创建型模式
    • 单利模式、工厂模式、抽象工厂模式、建造者模式、原型模式
  • 结构型模式
    • 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
  • 行为模式
    • 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式

创建型模式

单例模式

  • 单例模式的优点

    • 单利模式只生成一个实例,减少了系统的性能开销。当一个对象的产生需要比较多的系统资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时产生一个单利对象,然后永久驻留内存的方式来解决。
    • 单利可以在系统设置全局的访问点,优化共享资源访问例如可以设计一个单利类,负责所有数据表的映射处理。
  • 常见的五种单利模式的实现方式

    • 主要
      • 恶汉式(线程安全,调用效率高。但是,不能延时加载)
        • 枚举式
      • 懒汉式(线程安全,调用效率不高。但是,可以延时加载)
        • 双重检测锁
        • 静态内部类式

1.恶汉式

/*
1.恶汉式
2.类加载时就初始化
3.效率极高
 */
public class Singleton4 {
    //私有构造函数
    private Singleton4(){}
    //创建自身对象,线程安全
    private final static Singleton4 instance=new Singleton4();
    //对外提供获取实例的方法
    public static Singleton4 getInstance(){
        return instance;
    }
    //测试
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            new Thread(()->{
              //同一个类得到的对象hashcode相同说明对象相同。
              //并不是说hashcode相同对象就相同,前提是同一个类生成的对象,hashcode相同,则对象相同。
                System.out.println(Singleton4.getInstance().hashCode());
            }).start();
        }
    }
}

2.懒加载(双重锁)

/*
1.懒加载
2.线程安全
3.相对复杂
 */
public class Singleton2 {
    //私有构造函数
    private Singleton2(){}
    //变量保存创建的对象,volatile禁止指令重排,防止虽然instance不为null,但是实例并未创建完成的问题。
    private static volatile Singleton2 instance=null;
    //对外提供获取对象的方法
    public static Singleton2 getInstance() {
        //双重判断
        if(instance==null){
            synchronized (Singleton2.class){
                if(instance==null){
                    instance=new Singleton2();
                }
            }
        }
        return instance;
    }
}

3.懒加载(内部静态类)

import java.util.HashSet;
import java.util.Set;
/*
1.懒加载的,加载外部类时不会加载内部类
2.线程安全
3.但是多创建了一个类
 */
public class Singleton1 {
    //私有构造函数
    private Singleton1(){}
    //创建内部静态类,创建对象
    private static class Singleton1Holder {
        private static final Singleton1 INSTANCE=new Singleton1();
    }
    //对外提供实例的方法
    public static Singleton1 getInstance() {
        return Singleton1Holder.INSTANCE;
    }

    //测试
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            new Thread(()->{
                //同一个类得到的对象hashcode相同说明对象相同。
                System.out.println(Singleton1.getInstance().hashCode());
            }).start();
        }
    }
}

4.枚举单例

/*
1.最完美的
2.能防止反序列化
3.线程安全
4.不符合创建对象的习惯
5.前面的方法,都可以通过找到class通过反射获取新的对象,但这个枚举没有构造函数,能防止反序列化。
 */
public enum Singleton3 {
    INSTANCE;

    //测试
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            new Thread(()->{
                //同一个类得到的对象hashcode相同说明对象相同。
                System.out.println(Singleton3.INSTANCE.hashCode());
            }).start();
        }
    }
}

反射破解单例模式

注意,枚举是不能破解的

public class Client {
    public static void main(String[] args) throws Exception{
        Singleton2 instance = Singleton2.getInstance();
        System.out.println(instance);
        Class<Singleton2> clazz = (Class<Singleton2>) Class.forName("com.yc.design.singleton.Singleton2");
        Constructor<Singleton2> declaredConstructor = clazz.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//跳过权限检测
        Singleton2 singleton2 = declaredConstructor.newInstance();//用跳过检测的构造器创建对象
        System.out.println(singleton2);
    }
}

反射可以破解单例,如果想不被破解,就要在单例的构造器上做手脚加上如下代码即可

private Singleton2(){
    if(instance!=null){
        throw new RuntimeException();
    }
}

反序列化破解单例模式

public class Singleton2 implements Serializable //首先要实现序列化接口
public class Client {
    public static void main(String[] args) throws Exception {
        Singleton2 instance = Singleton2.getInstance();
        Singleton2 instance2 = Singleton2.getInstance();
        System.out.println(instance);
        System.out.println(instance2);
        //序列化,把对象序列化到硬盘上
        FileOutputStream fos = new FileOutputStream("D:/abc.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(instance);
        oos.close();
        fos.close();
        //反序列化,从硬盘中读取对象
        FileInputStream fis = new FileInputStream("D:/abc.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Singleton2 s2 = (Singleton2) ois.readObject();
        System.out.println(s2);
    }
}

避免被序列化重新创建新对象的方法,在单例类中写下这个方法即可,反序列化时会自动调用此方法。

//反序列化时,如果定义了readResolve()方法则直接返回此方法指定的对象,不需要再单独创建新对象。
private Object readResolve() {
    return instance;
}

工厂模式

实际开发中用简单工厂比较多,用工厂方法和抽象工厂比较少。

  • 要点
    • 简单工厂模式(静态工厂模式)
      • 虽然某种程度不符合设计模式,但实际使用较多。
    • 工厂方法模式
      • 不修改已有类的前提下,通过增加新的工厂类实现扩展。但是增加大量的类,变得复杂不简洁。
    • 抽象工厂模式
      • 不可以增加产品,可以增加产品族!

1.简单工厂

  • 缺点
    1. 新增实例时必须修改工厂中的方法,添加对新实例的支持,这违背开闭原则。

1.首先定义一个接口

public interface Car {
    public void run();
}

2.定义实体类,实现接口

public class Baoma implements Car {//创建宝马
    @Override
    public void run(){
        System.out.println("宝马。。。");
    }
}
public class Dazhong implements Car {
    @Override
    public void run() {
        System.out.println("大众。。。");
    }
}

3.创建工厂,来生产各种需要的实例

public class CarFactory {
    public static Car getCar(String carName){
        switch (carName){
            case "宝马":
                return new Baoma();
            case "大众":
                return new Dazhong();
        }
        return null;
    }
}

4.客户端调用

public class Client {
    public static void main(String[] args) {
        Car b = CarFactory.getCar("宝马");
        Car d = CarFactory.getCar("大众");
        b.run();
        d.run();
    }
}

2.抽象工厂

抽象工厂生产的是一个产品族,不是单个产品。

1.创建引擎的接口,同时引擎有好的和不好的,一并创建了。

public interface Engine {
    void start();
    void run();
}
class LuxuryEngine implements Engine{
    @Override
    public void start() {
        System.out.println("启动快!");
    }
    @Override
    public void run() {
        System.out.println("跑得快!");
    }
}
class LowEngine implements Engine{
    @Override
    public void start() {
        System.out.println("启动慢");
    }
    @Override
    public void run() {
        System.out.println("跑得慢");
    }
}

2.创建座椅的接口,座椅同样分好的和一般的

public interface Seat {
    void effect();
}

class LuxurySeat implements Seat {
    @Override
    public void effect() {
        System.out.println("能按摩的座椅");
    }
}

class LowSeat implements Seat {
    @Override
    public void effect() {
        System.out.println("普通座椅");
    }
}

3.创建汽车的工厂,汽车也根据情况定制好的车,和一般的车等

public interface CarFactory {
    Engine createEngine();
    Seat createSeat();
}

class LuxuryCar implements CarFactory {
    @Override
    public Engine createEngine() {
        return new LuxuryEngine();
    }
    @Override
    public Seat createSeat() {
        return new LuxurySeat();
    }
}

class LowCar implements CarFactory {
    @Override
    public Engine createEngine() {
        return new LowEngine();
    }
    @Override
    public Seat createSeat() {
        return new LowSeat();
    }
}

4.创建客户端,自定义创建好的或坏的

public class Client {
    public static void main(String[] args) {
        CarFactory cf=new LuxuryCar();
        Engine engine = cf.createEngine();
        engine.run();
        engine.start();
        CarFactory lowCar = new LowCar();
        Engine engine1 = lowCar.createEngine();
        engine1.start();
        engine1.run();
    }
}

建造者模式

**我的理解:**创建的东西的组件必须相同,比如人,如果模板是头、手、身体、脚,那么创建的所有人必须都是这个模板,也就是说模板不能修改,这个是大缺点。但是手可以是强壮的手,残疾的手,等,每个单独的组件可以独自定义,创建的顺序也可以定义,可以先创建头,可以先创建手等。

  • 场景
    • 需要建造一个复杂的产品,比如手机。这个装配子组件有个问题需要处理,装配子组件是不是有步骤问题?
  • 建造模式的本质
    • 分离了对象子组件的单独构造(由Builder负责)和装配(由Director负责)从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况下使用。
    • 由于实现来构建和装配的解耦,不同的构建器相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。

介绍

**意图:**将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

**主要解决:**主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。

**何时使用:**一些基本部件不会变,而其组合经常变化的时候。

**如何解决:**将变与不变分离开。

**关键代码:**建造者:创建和提供实例,导演:管理建造出来的实例的依赖关系。

应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。

优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

**注意事项:**与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

代码

更复杂的方式请参考菜鸟教程中的建造者模式代码示例

1、创建一个人的模板,这个模板是不可随便修改的

public class Humon {
    //可以把这个换成一个接口,接口的实现类是一类东西,具体可以参考菜鸟教程的建造者模式。
    public String head;
    public String body;
    public String hand;
    public String foot;

    public String getHead() {
        return head;
    }

    public void setHead(String head) {
        this.head = head;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }

    public String getHand() {
        return hand;
    }

    public void setHand(String hand) {
        this.hand = hand;
    }

    public String getFoot() {
        return foot;
    }

    public void setFoot(String foot) {
        this.foot = foot;
    }

    @Override
    public String toString() {
        return "Humon{" +
                "head='" + head + '\'' +
                ", body='" + body + '\'' +
                ", hand='" + hand + '\'' +
                ", foot='" + foot + '\'' +
                '}';
    }
}

2.创建一个创建人的接口

public interface BuilderHumon {
    String createHead();
    String createbody();
    String createhand();
    String createfoot();
}

3.创建一个实现创建接口的方法,创建的逻辑可能不同,但是创建的东西必须相同或类似

public class BuilderHumonStrong implements BuilderHumon {
    @Override
    public String createHead() {
        String head = "聪明的脑袋";
        System.out.println(head);
        return head;
    }

    @Override
    public String createbody() {
        String body = "强壮的身体";
        System.out.println(body);
        return body;
    }

    @Override
    public String createhand() {
        String hand = "有力的手";
        System.out.println(hand);
        return hand;
    }

    @Override
    public String createfoot() {
        String foot = "强健的脚";
        System.out.println(foot);
        return foot;
    }
}

4.创建一个组装的接口,组装的顺序可以不同

public interface DirectorHumon {
    Humon directorHumon(BuilderHumon builderHumon);
}

5.创建实现类组装接口的实体类,里面是具体的顺序以及实现。

/**
 * 装配是有一定顺序的,可以自定义装配的顺序,本处只是举个例子
 */
public class DirectorHumonOrderOne implements DirectorHumon{
    @Override
    public Humon directorHumon(BuilderHumon builderHumon) {
        String foot = builderHumon.createfoot();
        String body = builderHumon.createbody();
        String hand = builderHumon.createhand();
        String head = builderHumon.createHead();
        Humon humon=new Humon();
        humon.setFoot(foot);
        humon.setBody(body);
        humon.setHand(hand);
        humon.setHead(head);
        return humon;
    }
}

6.客户端调用示例

public class Client {
    public static void main(String[] args) {
        DirectorHumon dh=new DirectorHumonOrderOne();
        Humon humon = dh.directorHumon(new BuilderHumonStrong());
        System.out.println(humon);
    }
}

7.返回结果

强健的脚
强壮的身体
有力的手
聪明的脑袋
Humon{head='聪明的脑袋', body='强壮的身体', hand='有力的手', foot='强健的脚'}

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,比如:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则采用这种模式。

介绍

**意图:**用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

**主要解决:**在运行期建立和删除原型。

何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

**如何解决:**利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码: 1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。

优点: 1、性能提高。 2、逃避构造函数的约束。

缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

**注意事项:**与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

浅克隆

1.创建抽象类,实现clonable接口

/**
 * 实现cloneable接口,以实现对本对象的克隆
 */
public abstract class Food implements Cloneable{
    public String type;//食物种类

    public void eatFoodMethod(){//吃食物的默认方法,用筷子吃
        System.out.println("用筷子吃"+type);
    }
    //极为重要的克隆方法,这个是object类自带的本地方法,直接调用c代码,效率极高
    @Override
    protected Food clone() throws CloneNotSupportedException {
        System.out.println("默认的克隆方法");
        Food clone = (Food) super.clone();
        return clone;
    }

    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
}

2.创建蔬菜类,继承食物抽象类

public class Vegetable extends Food {
   public Vegetable(){
       type = "蔬菜";
   }

    @Override
    public void eatFoodMethod() {
        System.out.println("我用叉子吃蔬菜");
    }
}

3.创建鸡肉类,继承食物抽象类

public class Chicken extends Food{
    private Date date;

    public Chicken (){
        type = "鸡肉";
    }
    //对于引用类型的复制,如果不进行深度克隆,那么在克隆前后引用类型的修改会波及克隆前后的对象。
    //1.深度克隆需要实现序列化接口,把对象转换为一个流,这个流是对原对象的拷贝,修改源对象不会影响,本处只是浅拷贝
    //2.深度克隆的另外一个方式是对克隆内容进行再克隆。如果a中有b,b也是引用类型,b中有c,c也是引用类型等用这个clon方法就比较麻烦
    @Override
    protected Food clone() throws CloneNotSupportedException {
        System.out.println("鸡肉的克隆方法");
        Chicken food= (Chicken)super.clone();
        //如果对date进行克隆
        food.date= (Date) this.date.clone();
        return food;
    }
}

4.创建实体缓存类

public class FoodCache {

    private static Map<String,Food> foodMap=new ConcurrentHashMap<>();

    public static Food getFood(String foodId) throws CloneNotSupportedException {
        Food food = foodMap.get(foodId);
        return food.clone();//获取时调用克隆方法
    }

    /**
     * 对每种情况都运行各种检查组装等复杂耗时的步骤,来创建对象。
     * 如此一来,我们把对象保存起来,用的时候克隆一份,可以做到高效创建。
     * 提示:如果用的spring,可以用策略加工厂来注册这个
     */
    public static void loadFood(){
        Vegetable vegetable=new Vegetable();
        foodMap.put("v",vegetable);
        Chicken chicken=new Chicken();
        foodMap.put("c", chicken);
    }

}

5.客户端调用

public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        FoodCache.loadFood();//把所有需要的食物加载到map缓存中
        Food food = FoodCache.getFood("v");
        System.out.println(food);
        Food food1 = FoodCache.getFood("v");
        System.out.println(food1);
        Chicken food2 = (Chicken) FoodCache.getFood("c");
        System.out.println(food2);
        Chicken food3 = (Chicken) FoodCache.getFood("c");
        System.out.println(food3);
    }
}

深克隆(通过Cloneable)

  • 要对对象A进行克隆,必须对A对象中的引用数据类型B也克隆。而且B也必须实现Cloneable接口,并实现clone()方法。

1.创建草对象,作为引用数据类型

public class Grass implements Cloneable{
    private  String type;

    public Grass(String type) {
        this.type = type;
    }

    @Override
    protected Grass clone() throws CloneNotSupportedException {
        return (Grass) super.clone();
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

2.创建绵羊原对象

public class Sheep implements Cloneable {
    private String name;
    private Grass grassType;

    @Override
    protected Sheep clone() throws CloneNotSupportedException {
        Sheep clone = (Sheep) super.clone();
        clone.setGrassType(grassType.clone());//注意,本对象内的其他对象也必须调用克隆方法
        return clone;
    }

    public String getName() {
        return name;
    }

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

    public Grass getGrassType() {
        return grassType;
    }

    public void setGrassType(Grass grassType) {
        this.grassType = grassType;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", grassType=" + grassType.getType() +
                '}'+ "hashCode=" + this.hashCode();
    }
}

3.创建客户端示例代码

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Grass g=new Grass("青草");
        Sheep s=new Sheep();
        s.setName("wuj");
        s.setGrassType(g);
        Sheep clone = s.clone();
        s.getGrassType().setType("黄草");//把对原对象的引用Grass修改后,不影响克隆对象
        System.out.println(s);//打印原对象
        System.out.println(clone);//打印克隆对象

    }
}

4.输出结果

Sheep{name='wujing', grassType=黄草}hashCode=1296064247
Sheep{name='wujing', grassType=青草}hashCode=1637070917

深克隆(通过序列化)

  • 序列化是把对象A转化为流放到内存中,同时A内的引用对象B等也放到了内存中,这种方式可以不用实现Cloneable接口但是要实现序列化接口,并且对于A的属性是引用数据类型的也要实现Serializable接口,对于对象内部多引用的克隆最简单,并且不容易出错。

1.创建对象

public class Sheep implements Serializable {
    private String name;
    private Date birthday;

    public String getName() {
        return name;
    }

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

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}

2.写一个克隆的方法,任意对象都适用

public static <T> T clone(T t) {
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(t);
            bais = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(bais);
            return (T) ois.readObject();
        } catch (IOException e) {
            throw new RuntimeException("Clone Object failed in IO.", e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("Class not found.", e);
        } finally {
            try {
                if (baos != null) baos.close();
                if (bais != null) bais.close();
                if (oos != null) oos.close();
                if (ois != null) ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

3.写客户端代码进行试验

public static void main(String[] args) {
    Date birthday = new Date(11111111l);
    Sheep s=new Sheep();
    s.setBirthday(birthday);
    s.setName("死竞");
    //克隆一个
    Sheep clone = clone(s);
    //打印原对象和克隆的对象
    System.out.println(s);
    System.out.println(clone);
    System.out.println("--------------------");
    s.getBirthday().setTime(222222222l);
    System.out.println(s);
    System.out.println(clone);
}

4.打印结果

Sheep{name='死竞', birthday=Thu Jan 01 11:05:11 CST 1970}
Sheep{name='死竞', birthday=Thu Jan 01 11:05:11 CST 1970}
--------------------
Sheep{name='死竞', birthday=Sat Jan 03 21:43:42 CST 1970}
Sheep{name='死竞', birthday=Thu Jan 01 11:05:11 CST 1970}

结构型模式

这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

  • **核心作用:**从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题。

适配器模式(adapter)

  • 工作中的场景
    • 经常用来做旧系统改造和升级。比如:系统要从1.0版本升级到2.0版本,但是其中的大量接口不想改动,因为已经有了用户,但是又不适用于新的需求,此时可以开发大量的适配器解决这个问题。
  • 用到适配器的代码
    • java.io.InputStreamReader(InputStream)字节流要转化为字符流
    • java.io.OutputStreamWriter(OutputStream)字节流要转化为字符流
  • 模式中的角色
    • 目标接口(Target):客户所期待的接口,目标可以是具体类抽象类或接口
    • 需要适配的类:需要适配的类或适配者类
    • 适配器:通过包装一个需要适配的对象,把原接口转换成目标接口

介绍

**意图:**将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

**主要解决:**主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。

何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

**如何解决:**继承或依赖(推荐)。

**关键代码:**适配器继承或依赖已有的对象,实现想要的目标接口。

应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。

优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。

**使用场景:**有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

**注意事项:**适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uq2JqQqJ-1573743750794)(E:\OneDrive\笔记\markdown\picture\image-20191105230041225.png)]

1.有个真实的被调用的类,相当于ps/2接口的键盘

/**
 * 真实的接口,相当于ps/2键盘
 * 想在的电脑一般只支持usb键盘,所以需要适配器才能用
 */
public class RealClass{
    public void request() {
        System.out.println("真实的类完成了请求!");
    }
}

2.创建一个接口,相当于电脑能用的只能是这样的usb口。

public interface Target {
    void requestHandler();
}

3.创建适配器,相当于可以ps/2与usb互相转化的连接通道

/**
 * 适配器:相当于usb与ps/2的转接头
 */
public class Adapter extends RealClass implements Target{
    /**
     * 这里也可以继承RealClass,拥有其所有有权限的方法,
     * 但是java不支持多继承,最好还是通过其他方法把类注入进来
     */
    private RealClass realClass;

    public Adapter(RealClass realClass) {
        this.realClass = realClass;
    }
    /**
     * 这个方法是外部真实需要的,就相当于那个usb,电脑只支持这个
     */
    @Override
    public void requestHandler() {
        realClass.request();//通过调用适配器类,适配器类在调用真实的接口
    }
}

4.客户端调用,相当于只有usb口的电脑

/**
 * 客户端类,相当于只支持usb接口的电脑
 */
public abstract class Client implements Target {
    public static void main(String[] args) {
        RealClass rc=new RealClass();
        Target target = new Adapter(rc);
        target.requestHandler();
    }
}

5.控制台返回数据

真实的类完成了请求!

代理模式(proxy)*

包含的角色

  • 抽象角色
    • 定义代理角色和真实角色的公共对外方法
  • 真实角色
    • 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用,关注真正的业务逻辑
  • 代理角色
    • 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制放到代理角色中处理

介绍

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

**主要解决:**在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

**何时使用:**想在访问一个类时做一些控制。

**如何解决:**增加中间层。

**关键代码:**实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

**使用场景:**按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。

注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

静态代理

image-20191105213018703

1.创建一个接口类,代理类和被代理类都要实现这个接口

public interface Star {
    void confer();          //商谈
    void signContract();    //签订合同
    void bookTicket();      //订票
    void sing();            //唱歌
    void collectMoney();    //收款
}

2.创建被代理类,实现接口

public class RealStar implements Star {
    @Override
    public void confer() {
        System.out.println("RealStar商谈");
    }
    @Override
    public void signContract() {
        System.out.println("RealStar签订合同");
    }
    @Override
    public void bookTicket() {
        System.out.println("RealStar订票");
    }
    @Override
    public void sing() {
        System.out.println("RealStar唱歌");
    }
    @Override
    public void collectMoney() {
        System.out.println("RealStar收款");
    }
}

3.创建代理类,实现接口

public class ProxyStar implements Star{
    /**
     * 在代理类中要注入被代理类,有一些操作必须由被代理类完成,
     * 在此处就是唱歌,其他操作都可以由代理类完成
     */
    private RealStar realStar;

    @Override
    public void confer() {
        System.out.println("ProxyStar商谈");
    }
    @Override
    public void signContract() {
        System.out.println("ProxyStar签订合同");
    }
    @Override
    public void bookTicket() {
        System.out.println("ProxyStar订票");
    }
    /**
     * 其他都可以由代理类完成,唯独这个必须要被代理类完成。
     */
    @Override
    public void sing() {
        if(realStar==null){
            realStar=new RealStar();
        }
        realStar.sing();
    }
    @Override
    public void collectMoney() {
        System.out.println("ProxyStar收款");

    }
}

4.客户端测试代码

public class Client {
    public static void main(String[] args) {
        ProxyStar proxyStar=new ProxyStar();
        proxyStar.confer();
        proxyStar.signContract();
        proxyStar.bookTicket();
        proxyStar.sing();
        proxyStar.collectMoney();
    }
}

5.打印输出,可以看到只有唱歌由真实的被代理类执行

ProxyStar商谈
ProxyStar签订合同
ProxyStar订票
RealStar唱歌
ProxyStar收款

动态代理

主要讲jdk自带的动态代理,本质就是在代理对象调用每个方法前或后,或前后,执行一段逻辑,起到一个增强的作用。

1、创建代理接口,和静态代理的相同

public interface Star {
    void confer();          //商谈
    void signContract();    //签订合同
    void bookTicket();      //订票
    void sing();            //唱歌
    void collectMoney();    //收款
}

2、创建真实的被代理对象,和静态代理相同

public class RealStar implements Star {
    @Override
    public void confer() {
        System.out.println("RealStar商谈");
    }
    @Override
    public void signContract() {
        System.out.println("RealStar签订合同");
    }
    @Override
    public void bookTicket() {
        System.out.println("RealStar订票");
    }
    @Override
    public void sing() {
        System.out.println("RealStar唱歌");
    }
    @Override
    public void collectMoney() {
        System.out.println("RealStar收款");
    }
}

3、创建代理类的Handler,要实现一个InvocationHandler

public class StarHandler implements InvocationHandler {
    private Star star;

    public StarHandler(Star star) {
        this.star = star;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object obj = null;
        System.out.println("调用RealStar之前");
        obj = method.invoke(star, args);//调用真实方法
        System.out.println("调用RealStar之后");
        return obj;
    }
}

4、客户端调用的代码

public class Client {
    public static void main(String[] args) {
        RealStar rs=new RealStar();
        StarHandler starHandler = new StarHandler(rs);
        Star star=(Star)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, starHandler);
        star.sing();
    }
}

5、控制台打印

调用RealStar之前
RealStar唱歌
调用RealStar之后

桥接模式(bridge)

场景描述:有电脑,分为台式机、笔记本、平板电脑。同时每个电脑又分为不同的品牌,联想、戴尔、宏碁、神舟等。如果分类时不拆开,类似联想笔记本,这样合在一起的方式,那么如果想新加一个,就必须在笔记本那加,还要在联想品牌那再加一遍,造成爆炸式的增长,随着维度的增加需要添加的类指数增长,这太傻比了。桥接模式就是把一个事物,按照不同的独立维度拆分开来,这样符合设计理念,增加哪个维度都不相互影响。并且只需要增加一遍即可。

桥接模式即将抽象部分与它的实现部分分离开来,使他们都可以独立变化。

桥接模式将继承关系转化成关联关系,它降低了类与类之间的耦合度,减少了系统中类的数量,也减少了代码量。

将抽象部分与他的实现部分分离这句话不是很好理解,其实这并不是将抽象类与他的派生类分离,而是抽象类和它的派生类用来实现自己的对象。这样还是不能理解的话。我们就先来认清什么是抽象化,什么是实现化,什么是脱耦。

**抽象化:**其概念是将复杂物体的一个或几个特性抽出去而只注意其他特性的行动或过程。在面向对象就是将对象共同的性质抽取出去而形成类的过程。

**实现化:**针对抽象化给出的具体实现。它和抽象化是一个互逆的过程,实现化是对抽象化事物的进一步具体化。

**脱耦:**脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。

对于那句话:将抽象部分与他的实现部分分离套用《大话设计模式》里面的就是实现系统可能有多个角度分类,每一种角度都可能变化,那么把这种多角度分类给分离出来让他们独立变化,减少他们之间耦合。

桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

设计模式_第1张图片

介绍

**意图:**将抽象部分与实现部分分离,使它们都可以独立的变化。

**主要解决:**在有多种可能会变化的情况下,用继承会造成类爆炸问题,扩展起来不灵活。

**何时使用:**实现系统可能有多个角度分类,每一种角度都可能变化。

**如何解决:**把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

**关键代码:**抽象类依赖实现类。

应用实例: 1、猪八戒从天蓬元帅转世投胎到猪,转世投胎的机制将尘世划分为两个等级,即:灵魂和肉体,前者相当于抽象化,后者相当于实现化。生灵通过功能的委派,调用肉体对象的功能,使得生灵可以动态地选择。 2、墙上的开关,可以看到的开关是抽象的,不用管里面具体怎么实现的。

优点: 1、抽象和实现的分离。 2、优秀的扩展能力。 3、实现细节对客户透明。

**缺点:**桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。

使用场景: 1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。 2、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。 3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。

image-20191106223338746 image-20191106224232034

1.创建一个电脑抽象类,同时创建他的几个不同的电脑的类型

public abstract class Computor {
    protected Brand brand;
    public Computor(Brand brand) {
        this.brand = brand;
    }
    protected void type(){
        brand.sale();
    }
}
class Desktop extends Computor{
    public Desktop(Brand brand) {
        super(brand);
    }
    //注意调用的顺序,必须在重写的方法中调用super.type().因为父抽象类的type中调用了品牌中的sale()方
    //法,这样子类就不用再调用了。在调用sale()方法前后可以自定义一些处理的方式
    @Override
    public void type() {
        System.out.print("销售台式机-");
        super.type();
    }
}
class Laptop extends Computor{
    public Laptop(Brand brand) {
        super(brand);
    }
    @Override
    public void type() {
        System.out.print("销售笔记本-");
        super.type();
    }
}
class Pad extends Computor{
    public Pad(Brand brand) {
        super(brand);
    }
    @Override
    public void type() {
        System.out.print("销售平板电脑-");
        super.type();
    }
}

2.创建品牌接口,同时创建几个不同的电脑品牌实现品牌的接口

public interface Brand {
    void sale();
}
class Lenovo implements Brand{
    @Override
    public void sale() {
        System.out.println("联想笔记本");
    }
}
class Dell implements Brand{
    @Override
    public void sale() {
        System.out.println("戴尔笔记本");
    }
}
class Shenzhou implements Brand{
    @Override
    public void sale() {
        System.out.println("神舟笔记本");
    }
}

3.创建客户端来调用代码

public class Client {
    public static void main(String[] args) {
        Computor a = new Desktop(new Dell());
        a.type();
        Computor b = new Laptop(new Lenovo());
        b.type();
        Computor c = new Pad(new Shenzhou());
        c.type();
    }
}

4.获取控制台返回结果,可以看到电脑的类型和品牌是拼合的。这就是桥接模式,类型和品牌是两个独立的维度。

销售台式机-戴尔笔记本
销售笔记本-联想笔记本
销售平板电脑-神舟笔记本

组合模式(composite)

  • 使用组合模式的场景

    • 把部分和整体的关系用树形结构来表示,从而使客户端可以使用统一的方式处理部分对象和整体对象
  • 应用场景

    • 操作系统的资源管理器
    • GUI中的容器层次图
    • XML文件解析
    • OA系统中,组织结构的处理
    • Junit中的单元测试框架(底层就是组合模式,TestCase叶子,TestUnite容器,Test接口(抽象))
  • 组合模式核心(基本代码示例如下)

    • 抽象构件(component)角色:定义了叶子和容器构件的共同点
    • 叶子(leaf)构件角色:无子节点
    • 容器(composite)构件角色:有容器特征,可以包含子节点
/**
 * 抽象组件
 */
public interface Component {
    void operation();//这个方法在容器构件和叶子节点都有
}
/**
 * 叶子组件
 */
interface Leaf extends Component{
}
/**
 * 容器组件
 */
interface composite extends Component{
    void add(Component component);//增加子组件
    void remove(Component component);//删除子组件
    Component getChildComponent(Integer index);
}

介绍

**意图:**将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性

**主要解决:**它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

**如何解决:**树枝和叶子实现统一接口,树枝内部组合该接口。

**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。

应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作符也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。

优点: 1、高层模块调用简单。 2、节点自由增加。

**缺点:**在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

**使用场景:**部分、整体场景,如树形菜单,文件、文件夹的管理。

**注意事项:**定义时为具体类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HvpSulSK-1573743750800)(E:\OneDrive\笔记\markdown\picture\image-20191107220820465.png)]

1.创建一个抽象文件接口,接口中的方法是所有子类共有的

/**
 * 组件
 */
public interface AbstractFile {
    //所有的子组件都有这个方法,所以不用其他特殊操作调用容器构件的这个方法可以递归到所有子节点
    //此即是统一的处理方式
    void printName();
}

2.创建容器Folder构件

/**
 * 容器构件(文件夹)
 */
public class Folder implements AbstractFile {
    private String name;
    private List<AbstractFile> childList = new ArrayList<>();
    public Folder(String name) {
        this.name = name;
    }
    public void add(AbstractFile abstractFile){
        childList.add(abstractFile);
    }
    public void remove(AbstractFile abstractFile){
        childList.remove(abstractFile);
    }
    public AbstractFile getChild(Integer index){
        return childList.get(index);
    }
    @Override
    public void printName() {
        System.out.println(name);
        //类递归操作
        for (AbstractFile abstractFile : childList) {
            abstractFile.printName();
        }
    }
}

3.创建叶子构件

/**
 * 叶子节点
 */
public class File implements AbstractFile{
    private String name;
    public File(String name) {
        this.name = name;
    }
    @Override
    public void printName() {
        System.out.println(name);
    }
}

4.创建客户端测试

public class Client {
    public static void main(String[] args) {
        //创建的都是叶子节点
        AbstractFile a,b,c,d,e,g,h;
        a = new File("美女.jpg");
        b = new File("帅哥.png");
        c = new File("少年的你.mp4");
        d = new File("肖申克的救赎.mp4");
        e = new File("异度空间.mp4");
        g = new File("笔记.txt");
        h = new File("设计模式.md");
        //创建的是容器构件,也即可以包含容器节点,也可以包含叶子节点
        Folder f1 = new Folder("---人物文件夹");
        f1.add(a); f1.add(b);
        Folder f2= new Folder("+++电影文件夹");
        f2.add(c); f2.add(d);f2.add(e);
        Folder f3= new Folder("***笔记文件夹");
        f3.add(g); f3.add(h);
        //调用最外层的容器构件的统一方法,会遍历所有子组件的所有统一方法
        Folder f4= new Folder("文件夹");
        f4.add(f1);f4.add(f2);f4.add(f3);
        f4.printName();
    }
}

5.打印结果

文件夹
---人物文件夹
美女.jpg
帅哥.png
+++电影文件夹
少年的你.mp4
肖申克的救赎.mp4
异度空间.mp4
***笔记文件夹
笔记.txt
设计模式.md

装饰器模式(decorator或wrapper)

  • 职责
    • 动态的为一个对象增加新的功能
    • 装饰器技术是一种代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀
  • 优点
    • 扩展对象功能比继承灵活,不会导致类个数急剧增加
    • 可以对一个类进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象
    • 具体构件类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类
  • 缺点
    • 产生很多小对象,大量小对象占据内存,一定程度上影响性能
    • 装饰模式易于出错,调试排查比较麻烦
  • 开发中碰到的场景
    • Io中输入流和输出流的设计
    • Swing包中图形界面构件功能
    • Servlet对象中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,增强了request对象的功能
    • Struts2中,request、response、session对象的处理
  • 装饰模式和桥接模式的区别
    • 两个模式都是解决过多子类对象的问题。但是他们的诱因不一样。桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定。装饰模式是基于一个稳定部分增加新的功能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZWpwex2-1573743750802)(E:\OneDrive\笔记\markdown\picture\image-20191107225731228.png)]

介绍

**意图:**动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

**主要解决:**一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

**何时使用:**在不想增加很多子类的情况下扩展类。

**如何解决:**将具体功能职责划分,同时继承装饰者模式。

关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。

应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

**优点:**装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

**缺点:**多层装饰比较复杂。

使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

**注意事项:**可代替继承。

代码

1.添加抽象构件角色

2.创建具体构件角色

3.创建装饰角色

4.创建具体装饰角色

/**
 * 抽象构件
 */
public interface Icar {
    void move();
}
/**
 * ConcreteComponent具体构件角色(真实对象)
 */
class Car implements Icar{
    @Override
    public void move() {
        System.out.println("陆地上跑!");
    }
}
/**
 * Decorator装饰角色
 */
class SuperCar implements Icar{
    private Icar icar;
    public SuperCar(Icar icar) {
        super();
        this.icar = icar;
    }
    @Override
    public void move() {
        icar.move();
    }
}

/**
 * 具体装饰角色(会飞的车)
 */
class FlyCar extends SuperCar{
    public FlyCar(Icar icar) {
        super(icar);
    }
    public void fly(){
        System.out.println("天上飞!");
    }
    @Override
    public void move() {
        super.move();
        fly();
    }
}
/**
 * 具体装饰角色(会游泳的车)
 */
class SwimCar extends SuperCar{
    public SwimCar(Icar icar) {
        super(icar);
    }
    public void swim(){
        System.out.println("水里游!");
    }
    @Override
    public void move() {
        super.move();
        swim();
    }
}

5.测试代码

public class Client {
    public static void main(String[] args) {
        Car car=new Car();
        car.move();
        System.out.println("------------------");
        Icar icar = new FlyCar(new SwimCar(new Car()));
        icar.move();
    }
}

6.打印结果

陆地上跑!
------------------
陆地上跑!
水里游!
天上飞!

外观模式(facade)

平时开发肯定不自觉的用到这种模式,不再详细展开

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

介绍

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

**主要解决:**降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。

何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个"接待员"即可。 2、定义系统的入口。

**如何解决:**客户端不与系统耦合,外观类与系统耦合。

**关键代码:**在客户端和复杂系统之间再加一层,这一层将调用顺序、依赖关系等处理好。

应用实例: 1、去医院看病,可能要去挂号、门诊、划价、取药,让患者或患者家属觉得很复杂,如果有提供接待人员,只让接待人员来处理,就很方便。 2、JAVA 的三层开发模式。

优点: 1、减少系统相互依赖。 2、提高灵活性。 3、提高了安全性。

**缺点:**不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

使用场景: 1、为复杂的模块或子系统提供外界访问的模块。 2、子系统相对独立。 3、预防低水平人员带来的风险。

**注意事项:**在层次化结构中,可以使用外观模式定义系统中每一层的入口。

**例子:**电脑整机是 CPU、内存、硬盘的外观。有了外观以后,启动电脑和关闭电脑都简化了。

直接 new 一个电脑。

在 new 电脑的同时把 cpu、内存、硬盘都初始化好并且接好线。

对外暴露方法(启动电脑,关闭电脑)。

启动电脑(按一下电源键):启动CPU、启动内存、启动硬盘

关闭电脑(按一下电源键):关闭硬盘、关闭内存、关闭CPU

享元模式(flyweight)

  • 场景

    • 内存属于稀缺资源,不要随便浪费,如果有很多个相同或相似的对象,我们可以通过享元模式节省内存
  • 核心

    • 享元模式以共享的方式高效的支持大量细粒度对象的重用
    • 享元模式能做到共享的关键是区分了内部状态和外部状态
      • 内部状态:可以共享,不会随环境变化而变化
      • 外部状态:不可以共享,会随着环境变化而变化
  • 优点

    • 极大减少内存中对象的个数
    • 相同或相似对象内存中只有一份,极大节约资源,提高系统性能
    • 外部状态相对独立,不影响内部状态
  • 缺点

    • 模式较为复杂,使程序逻辑复杂化
    • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态使运行时间变长。用时间换取了空间

介绍

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

**主要解决:**在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

**如何解决:**用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象。

**关键代码:**用 HashMap 存储这些对象。

应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。

**优点:**大大减少对象的创建,降低系统的内存,使效率提高。

**缺点:**提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。

注意事项: 1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。 2、这些类必须有一个工厂对象加以控制。

image-20191109161151061

代码

**描述:**创建一个棋类的享元接口,然后创建具体的享元类实现这个接口。这个享元类中有具体的外部状态与内部状态。创建一个享元类的工厂类,当需要特定颜色的棋子时直接从工厂中拿,但是棋子的位置也就是外部状态类需要自己穿进去,这样颜色类的享元类就与外部状态分开了。

1.以五子棋为例,创建享元类接口

/**
 * 享元类
 */
public interface ChessFlyweight {
    void display(Coordinate coordinate);
}

2.创建具体的享元类

public class ConcreteChess implements ChessFlyweight {
    private String color;
    private Coordinate c;

    public ConcreteChess(String color) {
        this.color = color;
    }

    @Override
    public void display(Coordinate coordinate) {
        this.c=coordinate;
        System.out.println("棋子颜色:"+color+"-----位置:"+c.toString());
    }
}

3.创建外部状态类

/**
 * 外部状态类(unshared concrete flyweight)
 */
public class Coordinate {
    private Integer x,y;

    public Coordinate(Integer x, Integer y) {
        this.x = x;
        this.y = y;
    }

    public Integer getX() {
        return x;
    }

    public void setX(Integer x) {
        this.x = x;
    }

    public Integer getY() {
        return y;
    }

    public void setY(Integer y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Coordinate{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

4.创建具体享元类工厂

import java.util.HashMap;
import java.util.Map;

public class ConcreteChessFactory {
    private static final Map<String, ConcreteChess> cc = new HashMap<>();

    public static ChessFlyweight getChess(String color){
        if(cc.get(color)!=null){
            return cc.get(color);
        }else {
            ConcreteChess value = new ConcreteChess(color);
            cc.put(color, value);
            return value;
        }
    }
}

5.客户端代码

public class Client {
    public static void main(String[] args) {
        ChessFlyweight b1 = ConcreteChessFactory.getChess("black");
        ChessFlyweight b2 = ConcreteChessFactory.getChess("black");
        System.out.println(b1==b2);
        //这个传进来的Coordinate是外部状态,外部状态传进来
        //享元模式的主要目的是达到节省内存的目的
        b1.display(new Coordinate(1,1));
        b2.display(new Coordinate(2,2));
        System.out.println(b1==b2);
    }
}

6.输出结果

true
棋子颜色:black-----位置:Coordinate{x=1, y=1}
棋子颜色:black-----位置:Coordinate{x=2, y=2}
true

行为型模式

责任链模式(chain of responsibility)

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

  • 定义
    • 讲能够处理同一类请求的对象连成一条链,所提交的请求沿着链传递,链上的对象逐个判断是否有能力处理该请求,如果能则处理,如果不能则传递给链上的下一个对象。
  • 场景
    • 打牌时轮流出牌
    • 接力赛跑
    • 大学中,奖学金审批
    • 公司中,公文审批

介绍

**意图:**避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

**何时使用:**在处理消息的时候以过滤很多道。

**如何解决:**拦截的类都实现统一接口。

**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

**注意事项:**在 JAVA WEB 中遇到很多应用。

代码

  • 公司中请求条的审批过程
    • 请假天数小于3天,主任审批
    • 请假天数大于等于3天,小于10天,经理审批
    • 请假天数大于等于10天,小于30天,总经理审批
    • 请假天数大于等于30天,拒绝
image-20191109181743906

1.创建要被处理的请求类,此处即请假对象

public class LeaveRequest {
    private String employeeName;//员工名称
    private Integer leaveDays;//请假天数
    private String reason;//请假原因
    public LeaveRequest(String employeeName, Integer leaveDays, String reason) {
        this.employeeName = employeeName;
        this.leaveDays = leaveDays;
        this.reason = reason;
    }
    public String getEmployeeName() {
        return employeeName;
    }
    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }
    public Integer getLeaveDays() {
        return leaveDays;
    }
    public void setLeaveDays(Integer leaveDays) {
        this.leaveDays = leaveDays;
    }
    public String getReason() {
        return reason;
    }
    public void setReason(String reason) {
        this.reason = reason;
    }
}

2.创建处理请求的抽象类,里面的处理方法是抽象方法,供所有处理者单独定义

/**
 * 抽象类
 */
public abstract class Leader {
    protected  String leaderName;//领导名字
    protected  Leader nextHandleLeader;//责任链上的后继对象
    public Leader(String leaderName) {
        this.leaderName = leaderName;
    }
    //设定责任链上的后继对象
    public void setNextHandleLeader(Leader nextHandleLeader) {
        this.nextHandleLeader = nextHandleLeader;
    }
    //处理请求的核心业务方法
    abstract void handleRequest(LeaveRequest leaveRequest);
}

3.处理者主任类,继承领导抽象类

/**
 * 主任
 */
public class Director extends Leader {
    public Director(String leaderName) {
        super(leaderName);
    }
    @Override
    void handleRequest(LeaveRequest leaveRequest) {
        if (leaveRequest.getLeaveDays() < 3) {
            System.out.println("员工:" + leaveRequest.getEmployeeName() + ",请假" + leaveRequest.getLeaveDays() + "天,原因:" + leaveRequest.getReason());
            System.out.println("主任:" + this.leaderName + ",同意!");
        } else {
            if (this.nextHandleLeader != null) {
                nextHandleLeader.handleRequest(leaveRequest);
            }
        }
    }
}

4.创建经理处理者,继承领导抽象类

/**
 * 经理
 */
public class Manager extends Leader {
    public Manager(String leaderName) {
        super(leaderName);
    }
    @Override
    void handleRequest(LeaveRequest leaveRequest) {
        if (leaveRequest.getLeaveDays() < 10) {
            System.out.println("员工:" + leaveRequest.getEmployeeName() + ",请假" + leaveRequest.getLeaveDays() + "天,原因:" + leaveRequest.getReason());
            System.out.println("经理:" + this.leaderName + ",同意!");
        } else {
            if (this.nextHandleLeader != null) {
                nextHandleLeader.handleRequest(leaveRequest);
            }
        }
    }
}

5.创建总经理处理者,继承领导抽象类

/**
 * 总经理
 */
public class GeneralManager extends Leader {
    public GeneralManager(String leaderName) {
        super(leaderName);
    }
    @Override
    void handleRequest(LeaveRequest leaveRequest) {
        if (leaveRequest.getLeaveDays() < 30) {
            System.out.println("员工:" + leaveRequest.getEmployeeName() + ",请假" + leaveRequest.getLeaveDays() + "天,原因:" + leaveRequest.getReason());
            System.out.println("总经理:" + this.leaderName + ",同意!");
        } else {
            System.out.println("请假30天或30天以上,直接离职吧!");
        }
    }
}

6.客户端代码,具体请看注释

public class Client {
    public static void main(String[] args) {
        //要处理的请求
        LeaveRequest leaveRequest = new LeaveRequest("小王八蛋", 7, "有病");
        //处理请求者
        Director director = new Director("张三");
        Manager manager = new Manager("李四");
        GeneralManager generalManager = new GeneralManager("王五");
        //设置下一个责任链成员
        director.setNextHandleLeader(manager);
        manager.setNextHandleLeader(generalManager);
        //让主任审批请求,如果天数不同会走到对应的处理者
        director.handleRequest(leaveRequest);
    }
}

7.控制台输出结果

员工:小王八蛋,请假7天,原因:有病
经理:李四,同意!

迭代器模式(iterator)

介绍

**意图:**提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

**主要解决:**不同的方式来遍历整个整合对象。

**何时使用:**遍历一个聚合对象。

**如何解决:**把在元素之间游走的责任交给迭代器,而不是聚合对象。

**关键代码:**定义接口:hasNext, next。

**应用实例:**JAVA 中的 iterator。

优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

**缺点:**由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

使用场景: 1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。

**注意事项:**迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

image-20191109193834131

1.创建迭代器接口

/**
 * 自定义迭代器接口
 * @param 
 */
public interface MyIterator<T> {
    T first();//将游标指向第一个元素
    T last();//将游标指向最后一个元素
    void next();//将游标指向下一个元素
    Boolean hasNext();//判断是否存在下一个元素
    T getCurrentObj();//获取当前对象
}

2.创建具体聚合类

import java.util.ArrayList;
import java.util.List;

/**
 * 具体聚合类
 */
public class ConcreteMyAggregate<T> {
    private List<T> list = new ArrayList<>();
    public void add(T t) {
        list.add(t);
    }
    public void remove(T t) {
        list.remove(t);
    }
    //获取迭代器
    public MyIterator getIterator(){
        return new ConcreteIterator();
    }
    //使用内部类定义迭代器,可以直接使用外部类的属性
    private class ConcreteIterator implements MyIterator {
        private Integer cursor=0;//定义游标,用于记录遍历时的位置,初始值为0

        @Override
        public T first() {
            if(list.isEmpty()){
                return null;
            }
            return list.get(0);
        }

        @Override
        public T last() {
            if(list.isEmpty()){
                return null;
            }
            return list.get(list.size() - 1);
        }
        @Override
        public void next() {
            cursor++;
        }
        @Override
        public Boolean hasNext() {
            if (cursor < list.size()) {
                return true;
            }else {
                return false;
            }
        }
        @Override
        public T getCurrentObj() {
            if (cursor < list.size()) {
                return list.get(cursor);
            }
            return null;
        }
    }
}

3.客户端代码

public class Client {
    public static void main(String[] args) {
        ConcreteMyAggregate<String> list = new ConcreteMyAggregate();
        list.add("aaa");
        list.add("bbb");
        list.add("ccc");
        MyIterator iterator = list.getIterator();
        System.out.println("第一个是:" + iterator.first());
        System.out.println("最后一个是:" + iterator.last());
        while (iterator.hasNext()) {
            System.out.println(iterator.getCurrentObj());
            iterator.next();
        }
    }
}

4.控制台结果

第一个是:aaa
最后一个是:ccc
aaa
bbb
ccc

中介者模式

  • 核心
    • 如果一个系统中对象之间的联系呈现为网状结构,对象之间存在大量多对多的关系,将导致关系极其复杂,这些对象称为“同事对象”
    • 我们可以引入一个中介者对象,使各个同事对象只跟中介者对象打交道,将复杂的网络结构呈现为简单的星状结构image-20191109200259095

介绍

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

**主要解决:**对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

**何时使用:**多个类相互耦合,形成了网状结构。

**如何解决:**将上述网状结构分离为星型结构。

**关键代码:**对象 Colleague 之间的通信封装到一个类中单独处理。

应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。

优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则。

**缺点:**中介者会庞大,变得复杂难以维护。

使用场景: 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

**注意事项:**不应当在职责混乱的时候使用。

代码

image-20191109204115774

1.创建中介者接口

/**
 * 中介者接口
 */
public interface Mediator {
    void register(String deptName,Department department);//注册部门
    void command(String deptName);//向哪个部门发命令
}

2.定义真实的中介者

/**
 * 总经理,中介者的具体实现
 */
public class GeneralManager implements Mediator{
    private Map<String, Department> map = new HashMap<>();//各个部门注册中心
    @Override
    public void register(String deptName, Department department) {
        map.put(deptName, department);
    }
    @Override
    public void command(String deptName) {
        map.get(deptName).selfAction();
    }
}

3.同事类接口

/**
 * 同事类接口
 */
public interface Department {
    void selfAction();//做本部门的事情
    void outAction();//向总经理发出申请,调用中介者
}

4.具体同事类,研发部

/**
 * 研发部
 */
public class Development implements Department {
    private Mediator m;//持有中介者(总经理)的引用
    public Development(Mediator m) {
        this.m = m;
        m.register("development",this);//同事在中介那注册一下
    }
    @Override
    public void selfAction() {
        System.out.println("研究项目!");
    }

    @Override
    public void outAction() {
        System.out.println("研发部:最近手头有点紧!");
    }
}

5.具体同事类,金融部

/**
 * 金融部
 */
public class Financial implements Department {
    private Mediator m;
    public Financial(Mediator m) {
        this.m = m;
        m.register("financial", this);//向中介注册自己
    }
    @Override
    public void selfAction() {
        System.out.println("数钱数到手抽筋!");
    }
    @Override
    public void outAction() {
        System.out.println("施舍你点钱!");
    }
}
  1. 具体同事类,市场部
/**
 * 市场部
 */
public class Market implements Department{
    private Mediator m;
    public Market(Mediator m) {
        this.m = m;
        m.register("market",this);//市场部向中介者注册自己
    }
    @Override
    public void selfAction() {
        System.out.println("跑市场!");
    }
    @Override
    public void outAction() {
        System.out.println("跑市场发现有个项目需要研发!手头也有点紧!");
        m.command("financial");
        m.command("development");
    }
}

7.客户端调用代码

public class Client {
    public static void main(String[] args) {
        //定义中介者
        Mediator m=new GeneralManager();
        //定义同事
        Market market = new Market(m);
        Development development=new Development(m);
        Financial financial = new Financial(m);
        //示例
        market.selfAction();
        market.outAction();
    }
}

8.控制太输出

跑市场!
跑市场发现有个项目需要研发!手头也有点紧!
数钱数到手抽筋!
研究项目!

策略模式(strategy)

在spring框架中的必备策略加工厂请点击spring策略工厂技巧

策略模式对应于解决某一个问题的一个算法族允许用户从该算法族中任选一个算法解决某一个问题,同时可以方便的更换算法或者增加新的算法,并且由客户端决定调用哪个算法。

介绍

**意图:**定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。

**主要解决:**在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。

**何时使用:**一个系统有许多许多类,而区分它们的只是他们直接的行为。

**如何解决:**将这些算法封装成一个一个的类,任意地替换。

**关键代码:**实现同一个接口。

应用实例: 1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3、JAVA AWT 中的 LayoutManager。

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

**注意事项:**如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。

##/## 代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vSd6W6Bm-1573743750804)(E:\OneDrive\笔记\markdown\picture\image-20191110171731013.png)]

1.创建策略模式接口

/**
 * 策略模式的接口
 */
public interface Strategy {
    Double getPrice(Double standarPrice);
}

2.创建具体的四个算法族,包括对不同的客户的优惠策略

/**
 * 新顾客小批量购买的算法
 */
public class NewCustomerFewStrategy implements Strategy{
    @Override
    public Double getPrice(Double standarPrice) {
        System.out.println("新顾客小批量购买,原价!");
        return standarPrice;
    }
}
/**
 * 新客户大批量
 */
public class NewCustomerManyStrategy implements Strategy{
    @Override
    public Double getPrice(Double standarPrice) {
        System.out.println("新客户大批量,按95折!");
        return standarPrice*0.9;
    }
}
/**
 * 老客户小批量
 */
public class OldCustomerFewStrategy implements Strategy{
    @Override
    public Double getPrice(Double standarPrice) {
        System.out.println("老客户小批量,85折!");
        return standarPrice*0.85;
    }
}
/**
 * 老顾客大批量
 */
public class OldCustomerManyStrategy implements Strategy {
    @Override
    public Double getPrice(Double standarPrice) {
        System.out.println("老顾客大批量!");
        return standarPrice * 0.8;
    }
}

3.创建调用策略模式的上下文类,可以不用,但是为了调用的灵活最好是加上

/** 负责和具体的策略类交互
 * 具体的算法和直接的客户端调用分离,使得算法可以独立于客户端独立的变化
 * 策略模式可以和工厂方法结合使用!调用时更加灵活
 */
public class Context {
    private Strategy strategy;//当前的算法对象
    //调用时直接通过构造器把算法注入
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    //也可以通过set方法注入,自己取舍
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    public Double getPrice(Double standarPrice){
        return strategy.getPrice(standarPrice);
    }
}

4.客户端调用

public class Client {
    public static void main(String[] args) {
        //创建算法,本次演示只创建老客户大批量
        OldCustomerManyStrategy oldCustomerManyStrategy = new OldCustomerManyStrategy();
        //在调用算法时必须自己明确调用哪个算法
        Context context = new Context(oldCustomerManyStrategy);
        Double price = context.getPrice(800.0);
        System.out.println(price);
    }
}

5.控制台输出

老顾客大批量!
640.0

模板方法模式(template method)

它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现。这样,新的子类可以在不改变一个算法结构的前提下重新定义该算法的某些特定步骤

  • 核心
    • 处理某个流程的代码已经都具备,但是其中某个节点的代码暂时不能确定。因此我们采用工厂方法模式,将这个节点代码的实现转给子类完成。即:具体步骤父类中定义好,某具体实现延迟到子类中定义

介绍

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

**主要解决:**一些方法通用,却在每一个子类都重新写了这一方法。

**何时使用:**有一些通用的方法。

**如何解决:**将这些通用算法抽象出来。

**关键代码:**在抽象类实现,其他步骤在子类实现。

应用实例: 1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。 2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。 3、spring 中对 Hibernate 的支持,将一些已经定好的方法封装起来,比如开启事务、获取 Session、关闭 Session 等,程序员不重复写那些已经规范好的代码,直接丢一个实体就可以保存。

优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。

**缺点:**每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

使用场景: 1、有多个子类共有的方法,且逻辑相同。 2、重要的、复杂的方法,可以考虑作为模板方法。

**注意事项:**为防止恶意操作,一般模板方法都加上 final 关键词。

代码

1.创建模板方法抽象类

public abstract class BankTemplate {
    /**
     * 具体方法
     */
    public void takeNumber(){
        System.out.println("取号排队");
    }
    /**
     * 办理具体的业务,钩子方法
     * 这个的具体实现在子类中定义,其他都是父类中的,并且最终调用的流程也是定义在父类中
     * transact:处理的意思
     */
    public abstract void transact();
    /**
     * evaluage:评价评估的意思
     */
    public void evaluate(){
        System.out.println("反馈评分");
    }
    //模板方法,用final修饰,避免被子类重写
    public final void process(){
        this.takeNumber();
        this.transact();
        this.evaluate();
    }
}

2.创建模板抽象类的子类

//取钱
public class DrawMoney extends BankTemplate{
    @Override
    public void transact() {
        System.out.println("我要取款!");
    }
}
//存钱
public class SaveMoney extends BankTemplate {
    @Override
    public void transact() {
        System.out.println("我要存钱");
    }
}

3.客户端调用

public class Client {
    public static void main(String[] args) {
        DrawMoney drawMoney=new DrawMoney();
        drawMoney.process();//调用流程
        SaveMoney saveMoney = new SaveMoney();
        saveMoney.process();
    }
}

4.控制台输出结果

取号排队
我要取款!
反馈评分
取号排队
我要存钱
反馈评分

状态模式

  • 核心
    • 用于解决系统中复杂对象的状态转换,以及不同状态下行为的封装问题
  • 结构
    • Context环境类
      • 环境类中维护一个State对象,他是定义了当前的状态
    • State抽象状态类
    • ConcreteState具体状态类
      • 每个类封装了一个状态对应的行为

介绍

**意图:**允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

**主要解决:**对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

**何时使用:**代码中包含大量与对象状态有关的条件语句。

**如何解决:**将各种具体的状态类抽象出来。

**关键代码:**通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。

应用实例: 1、打篮球的时候运动员可以有正常状态、不正常状态和超常状态。 2、曾侯乙编钟中,‘钟是抽象接口’,'钟A’等是具体状态,'曾侯乙编钟’是具体环境(Context)。

优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。

**注意事项:**在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。

代码

image-20191110184857484

1.创建状态类接口

/**
 * 状态类接口
 */
public interface State {
    public void handle();//不同状态时调用的行为
}

2.空闲状态,预定状态,已入住状态分别实现接口

/**
 * 空闲状态
 */
public class FreeState implements State {
    @Override
    public void handle() {
        System.out.println("房间空闲!");
    }
}
/**
 * 预定状态
 */
public class BookedState implements State {
    @Override
    public void handle() {
        System.out.println("房间已经预定!");
    }
}
/**
 * 已入住状态
 */
public class CheckedInState implements State{
    @Override
    public void handle() {
        System.out.println("已经入住!");
    }
}

3.状态上下文类,根据传入的状态,调用不同的状态处理行为

public class HomeContext {
    private State state;
    public void setState(State state){
        this.state=state;
        System.out.println("修改状态---");
        //根据传入的状态,调用对应状态的具体行为
        state.handle();
    }
}

4.客户端代码调用

public class Client {
    public static void main(String[] args) {
        HomeContext homeContext = new HomeContext();
        //根据传进的不同状态调用对应状态的具体行为
        homeContext.setState(new FreeState());
        homeContext.setState(new CheckedInState());
        homeContext.setState(new BookedState());
    }
}

5.打印结果

修改状态---
房间空闲!
修改状态---
已经入住!
修改状态---
房间已经预定!

观察者模式

  • 场景

    • 聊天室程序的创建。服务器创建好后,a、b、c三个客户端连上来公开聊天。a向服务器发送数据,服务器端聊天数据改变。我们希望将这些聊天数据分别发给其他在线客户。也就是说,每个客户端需要更新服务器端的数据。
    • 网站上,很多人订阅一个主题新闻。当有这个主题新闻时,就会将这些新闻发给所有订阅的人。
    • 大家一起玩cs游戏时,服务器需要将每个人的方位变化发给所有的客户。

    以上场景,都可以用观察者模式处理。我们可以把多个订阅者,客户称之为观察者。需要同步给多个订阅者的数据封装到对象中,称之为目标。

  • 核心

    • 观察者模式主要用于1:N的通知。当一个对象(目标对象subject或observer)的状态变化时,他需要及时告知一系列对象(观察者对象,observer),令他们做出响应
    • 通知观察者的方式
      • 推:每次都会把通知以广播的方式发送给所有观察者,所有观察者只能被动接收。
      • 拉:观察者只要知道有情况即可。至于什么时候获取内容,获取什么内容,都可以自主决定。

介绍

**意图:**定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

**主要解决:**一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

**何时使用:**一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

**如何解决:**使用面向对象技术,可以将这种依赖关系弱化。

**关键代码:**在抽象类里有一个 ArrayList 存放观察者们。

应用实例: 1、拍卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。 2、西游记里面悟空请求菩萨降服红孩儿,菩萨洒了一地水招来一个老乌龟,这个乌龟就是观察者,他观察菩萨洒水这个动作。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

注意事项: 1、JAVA 中已经有了对观察者模式的支持类。 2、避免循环引用。 3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

代码1

image-20191110200714425

1.创建主题对象,观察者要订阅的主题

import java.util.ArrayList;
import java.util.List;

public class Subject {
    protected List<Observer> list = new ArrayList<>();

    public void registerObserver(Observer observer){
        list.add(observer);
    }

    public void removeObserver(Observer observer){
        list.remove(observer);
    }
    //通知所有观察者更新状态
    public void notifyAllObserver(){
        list.forEach(x->{
            x.update(this);
        });
    }
}

2.创建具体的主题对象

/**
 * 具体的主题对象
 */
public class ConcreateSubject extends Subject{
    private int state;
    public int getState(){
        return state;
    }

    public void setState(int state) {
        this.state=state;
        //主题对象(目标对象)值发生了变化,通知所有观察者
        this.notifyAllObserver();
    }
}

3.创建观察者对象接口

public interface Observer {
    void update(Subject subject);
}

4.创建观察者对象实现类

public class ObserverA implements Observer {
    //myState需要和目标对象的state保持一致
    private int myState;
    public int getMyState() {
        return myState;
    }
    public void setMyState(int myState) {
        this.myState = myState;
    }
    @Override
    public void update(Subject subject) {
        //这个更新操作将传进的主题对象的状态值赋给自己
        myState=((ConcreateSubject)subject).getState();
    }
}

5.客户端调用

public class Client {
    public static void main(String[] args) {
        //创建具体的主题对象
        ConcreateSubject cs = new ConcreateSubject();
        //创建观察者对象三个
        ObserverA a=new ObserverA();
        ObserverA b=new ObserverA();
        ObserverA c=new ObserverA();
        //将三个观察者注册到主题对象
        cs.registerObserver(a);
        cs.registerObserver(b);
        cs.registerObserver(c);
        //修改主题对象的state值
        cs.setState(200);
        //查看观察者中的状态值
        System.out.println(a.getMyState());
        System.out.println(b.getMyState());
        System.out.println(c.getMyState());
    }
}

6.控制台输出

200
200
200

代码2(使用jdk提供的观者接口)

1.创建主题对象

import java.util.Observable;

/**
 * 目标对象
 * Observable:是java提供的
 */
public class ConcreteSubject extends Observable {
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;//目标的对象发生了改变
        //以下是Observable对象内部实现的
        setChanged();//表示目标对象已经做了改变
        notifyObservers(state);//通知所有的观察者
    }
}

2.创建观察者对象

import java.util.Observable;
import java.util.Observer;

public class ObserverA implements Observer {
    private int mystate;
    @Override
    public void update(Observable o, Object arg) {
       mystate = ((ConcreteSubject)o).getState();//这个Observable就可以转换为具体的主题类
    }
    public int getMystate() {
        return mystate;
    }
    public void setMystate(int mystate) {
        this.mystate = mystate;
    }
}

3.创建客户端代码

public class Client {
    public static void main(String[] args) {
        //创建具体的主题对象
        ConcreteSubject cs = new ConcreteSubject();
        //创建观察者对象三个
        ObserverA a=new ObserverA();
        ObserverA b=new ObserverA();
        ObserverA c=new ObserverA();
        //将三个观察者注册到主题对象
        cs.addObserver(a);
        cs.addObserver(b);
        cs.addObserver(c);
        //修改主题对象的state值
        cs.setState(200);
        //查看观察者中的状态值
        System.out.println(a.getMystate());
        System.out.println(b.getMystate());
        System.out.println(c.getMystate());
    }
}

4.控制台输出结果

200
200
200

备忘录模式

  • 核心

    • 就是保存某个对象内部状态的拷贝,这样就可以将该对象回复到原先的状态
  • 结构

    • 源发器类Originator
    • 备忘录类Memento
    • 负责人类CareTake
  • 备忘点较多时

    • 将备忘录压栈

      public class CareTaker{
          private Memento memento;
          private Stack<Memento> stack=new Stack<>();//利用栈的特性,先进后厨,后进先出
      }
      
    • 将多个对象序列化和持久化

  • 开发中的应用场景

    • 棋类游戏中的悔棋
    • 普通软件中的,撤销操作
    • 数据库软件事物管理中的回滚操作
    • Photoshop中的历史记录

介绍

**意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

**主要解决:**所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

**何时使用:**很多时候我们总是需要记录一个对象的内部状态,这样做的目的就是为了允许用户取消不确定或者错误的操作,能够恢复到他原先的状态,使得他有"后悔药"可吃。

**如何解决:**通过一个备忘录类专门存储对象状态。

**关键代码:**客户不与备忘录类耦合,与备忘录管理类耦合。

应用实例: 1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。

优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。

**缺点:**消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

使用场景: 1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。

注意事项: 1、为了符合迪米特原则,还要增加一个管理备忘录的类。 2、为了节约内存,可使用原型模式+备忘录模式。

代码

image-20191114230125347

1.创建源对象

/**
 * 源对象
 */
public class Emp {
    private String name;
    private Integer age;
    private double salary;
//    //进行备忘操作,并返回备忘录对象
//    public EmpMemento memento(EmpMemento memento){
//        return new EmpMemento(this);
//    }
    //进行数据恢复,恢复成指定备忘录中的对象
    public void recovery(EmpMemento empMemento){
        this.name=empMemento.getName();
        this.age=empMemento.getAge();
        this.salary=empMemento.getSalary();
    }
    public Emp(String name, Integer age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
    @Override
    public String toString() {
        return "Emp{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

2.创建备忘录对象

/**
 * 备忘录类
 */
public class EmpMemento {
    private String name;
    private Integer age;
    private double salary;

    public EmpMemento(Emp emp) {
        this.name = emp.getName();
        this.age = emp.getAge();
        this.salary = emp.getSalary();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
}

3.创建负责人类

/**
 * 负责人类
 * 负责管理备忘录对象
 */
public class CareTake {
    private EmpMemento empMemento;
    //以后可以改成list存放备忘录对象,或Stack存放备忘录对象
//    List list = new ArrayList<>();
//    Stack stack = new Stack<>();

    public EmpMemento getEmpMemento() {
        return empMemento;
    }
    public void setEmpMemento(EmpMemento empMemento) {
        this.empMemento = empMemento;
    }
}

4.客户端对象

public class Client {
    public static void main(String[] args) {
        //创建备忘录负责人,等待接管备忘录
        CareTake careTake=new CareTake();
        //创建源对象
        Emp wj = new Emp("wj", 24, 1000);
        System.out.println("第一次打印"+wj);
        //对源对象做一个备份
        EmpMemento empMemento = new EmpMemento(wj);
        careTake.setEmpMemento(empMemento);
        //修改源对象
        wj.setName("sd");
        wj.setAge(100);
        wj.setSalary(-100);
        System.out.println("第二次打印"+wj);
        //恢复上一个备忘点
        wj.recovery(careTake.getEmpMemento());
        System.out.println("第三次打印"+wj);
    }
}
}
public Integer getAge() {
    return age;
}
public void setAge(Integer age) {
    this.age = age;
}
public double getSalary() {
    return salary;
}
public void setSalary(double salary) {
    this.salary = salary;
}
@Override
public String toString() {
    return "Emp{" +
            "name='" + name + '\'' +
            ", age=" + age +
            ", salary=" + salary +
            '}';
}

}


2.创建备忘录对象

```java
/**
 * 备忘录类
 */
public class EmpMemento {
    private String name;
    private Integer age;
    private double salary;

    public EmpMemento(Emp emp) {
        this.name = emp.getName();
        this.age = emp.getAge();
        this.salary = emp.getSalary();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public double getSalary() {
        return salary;
    }
    public void setSalary(double salary) {
        this.salary = salary;
    }
}

3.创建负责人类

/**
 * 负责人类
 * 负责管理备忘录对象
 */
public class CareTake {
    private EmpMemento empMemento;
    //以后可以改成list存放备忘录对象,或Stack存放备忘录对象
//    List list = new ArrayList<>();
//    Stack stack = new Stack<>();

    public EmpMemento getEmpMemento() {
        return empMemento;
    }
    public void setEmpMemento(EmpMemento empMemento) {
        this.empMemento = empMemento;
    }
}

4.客户端对象

public class Client {
    public static void main(String[] args) {
        //创建备忘录负责人,等待接管备忘录
        CareTake careTake=new CareTake();
        //创建源对象
        Emp wj = new Emp("wj", 24, 1000);
        System.out.println("第一次打印"+wj);
        //对源对象做一个备份
        EmpMemento empMemento = new EmpMemento(wj);
        careTake.setEmpMemento(empMemento);
        //修改源对象
        wj.setName("sd");
        wj.setAge(100);
        wj.setSalary(-100);
        System.out.println("第二次打印"+wj);
        //恢复上一个备忘点
        wj.recovery(careTake.getEmpMemento());
        System.out.println("第三次打印"+wj);
    }
}

总结

  • 行为模式
行为模式 作用
创建型模式 关注对象的创建过程
结构型模式 关注对象和类的组织
行为型模式 关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信和协作,进一步明确对象的职责,共11中模式
  • 结构型模式汇总
设计模式 作用
代理模式 为真实对象提供一个代理,从而控制对真实对象的
适配器模式 使原本由于接口不兼容不能一起工作的类可以一起工作
桥接模式 处理多层继承结构,处理多维度变化的场景,将各个维度设计成独立的继承结构,使各个维度可以独立的扩展,在抽象层建立关联
组合模式 将对象组合成树状结构,以表示部分和整体层次结构使得客户端可以统一的调用叶子对象和容器对象
装饰模式 动态的给一个对象添加额外的功能,比继承灵活
外观模式 为子系统提供统一的调用接口,使得子系统更加容器使用
享元模式 运用共享技术有效的实现管理大量细粒度对象,节省内存,提高效率

你可能感兴趣的:(JAVA)