Java 设计模式

学习笔记   2023-08-10 16:09   540   0  

设计模式 

设计模式分为三种类型,共23种

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式

  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

  3. 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)

设计模式的原则

 1. 面向抽象原则

 2. 开-闭原则

  • 设计应当对扩展开放,对修改关闭

  • 如果设计遵守了“开-闭原则”,那么这个设计一定是易维护的,因为在设计中增加新的模块时,不必去修改设计中的核心模块

 3. 多用组合少用继承原则

 4. 高内聚-低耦合原则

  • 如果类中的方法是一组相关的行为,则称该类是高内聚的,反之称为低内聚的

  • 所谓低耦合就是尽量不要让一个类含有太多的其它类的实例的引用,以避免修改系统的其中一部分会影响到其它部分

设计模式常用的七大原则有

  1. 单一职责原则

  2. 接口隔离原则

  3. 依赖倒转(倒置)原则

  4. 里氏替换原则

  5. 开闭原则

  6. 迪米特法则

  7. 合成复用原则

单例模式

单例模式注意事项和细节说明

  1. 单例模式保证了系统内存中该累只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法而不是new

  3. 单例模式使用的场景

    1. 需要频繁进行创建和销毁的对象

    2. 创建对象时耗时过多或耗费资源过多(重量级对象),但又经常用到的对象

    3. 工具类对象

    4. 频繁访问数据库或文件的对象(比如数据源、session工程等)

饿汉式(静态常量)

饿汉式(静态常量)应用实例

步骤如下:

  1. 构造器私有化(防止new)

  2. 类的内部创建对象

  3. 向外暴露一个静态的公共方法。getInstance

  4. 代码实现

public class SingletonTest01 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println(instance.hashCode() == instance2.hashCode());
    }}// 饿汉式(静态常量)class Singleton{
    // 1. 构造器私有化,防止外部new
    private Singleton(){
    }
    private final static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点说明:

  1. 优点:这种方法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题

  2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会导致内存的浪费

  3. 这种方式基于classloader机制避免了多线程的同步问题,但是,instance在类装载的时候就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)当中类的装载,这时候初始化instance就没有达到lazy loading的效果

  4. 结论:这种单例模式可用,可能造成内存的浪费

饿汉式(静态变量)

public class SingletonTest02 {

    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2); // true
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }}// 饿汉式(静态变量)class Singleton{
    // 1. 构造器私有化,防止外部new
    private Singleton(){
    }
    private static final Singleton instance;
    static {
        instance = new Singleton();
    }
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点说明

  1. 这种方法和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类的装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的

  2. 结论:这种单例模式可用,但是可能造成内存浪费

懒汉式(线程不安全)

class Singleton{
    private static Singleton instance;
    // 1. 构造器私有化,防止外部new
    private Singleton(){
    }

    // 提供一个静态的公有方法,当使用到该方法时候,才去创建instance
    // 即懒汉式
    public static Singleton getInstance(){
        if (instance == null){
           instance = new Singleton();
        }
        return instance;
    }
}

优缺点说明

  1. 起到了Lazy Loading的效果,但是只能在单线程下使用

  2. 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这是便会产生多个实例。所以在多线程环境下不可使用这种方法

  3. 结论:在实际开发中,不要使用这种方法

懒汉式(线程安全,同步方法)

使用synchronized解决线程不安全问题

class Singleton{
    private static Singleton instance;
    // 1. 构造器私有化,防止外部new
    private Singleton(){
    }

    // 提供一个静态的公有方法,当使用到该方法时候,才去创建instance
    // 即懒汉式
    // 加入同步处理的代码
    public static synchronized Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

优缺点说明:

  1. 解决了线程不安全问题

  2. 效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其他这个方法只执行一次实例化代码就够了,后面的想获得这类实例直接return就行了。方法进行同步效率太低

  3. 结论:在实际开发中,不推荐使用

懒汉式(线程安全,同步代码块)

class Singleton{
    private static Singleton instance;
    // 1. 构造器私有化,防止外部new
    private Singleton(){
    }

    // 提供一个静态的公有方法,当使用到该方法时候,才去创建instance
    // 即懒汉式
    // 加入同步处理的代码
    public static Singleton getInstance(){
        if (instance == null){
            synchronized (Singleton.class){
                instance = new Singleton();
            }
        }
        return instance;
    }
}

优缺点说明:

  1. 这种方法,本意是想对第四种方法进行优化,因为前面同步方法效率太低,改为同步产生实例化的代码块

  2. 但是这种同步并不能起到线程同步的作用。跟第3中实现方式遇到的情况一致

  3. 结论:在实际开发过程中,不能使用这种方法

双重检查

class Singleton{
    private static Singleton instance;
    // 1. 构造器私有化,防止外部new
    private Singleton(){
    }

    // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题 ,同时解决懒加载问题
    // 同时保证了效率,推荐使用
    public static Singleton getInstance(){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

优缺点说明:

  1. Double-Check是多线程开发中常使用的,如代码中所示,我们进行了两次instance == null的判断,这样就可以保证线程的安全了

  2. 这样,实例化代码只用执行一次,后面再次访问时,判断instance == null,直接return实例化对象,也避免了反复进行方法同步

  3. 线程安全;延迟加载;效率较高

  4. 结论:在实际开发中推荐使用

静态内部类

class Singleton{
    private Singleton(){}

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

优缺点说明:

  1. 这种方法采用了类装载的机制来保证初始化实例只有一个线程

  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化

  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的

  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

  5. 结论:推荐使用

枚举

enum Singleton{
    INSTANCE; //属性
    public void syaOk(){
        System.out.println("ok");
    }
}

优缺点说明

  1. 借助JDK1.5添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

  2. 这种方式是Effective java作者Josh Bloch提倡的方式

  3. 结论:推荐使用

工厂方法模式

工厂方法模式的结构中包括四种角色

  1. 抽象产品(Product):抽象类或接口,负责定义具体产品必须实现的方法

  2. 具体产品(ConcreteProduct):具体产品是一个类,如果Product是一个抽象类,那么具体产品就是Product的子类;如果Product是一个接口,那么具体产品是实现Product接口的类

  3. 构造者(Creator):一个接口或抽象类。构造这负责定义一个称作工厂方法的抽象类,该方法返回具体产品类的实例

  4. 具体构造者(ConcreteCreator):具体构造这重写工厂方法,是该方法返回具体的产品实例

public class Main {

    public static void main(String[] args) {
        PenCore penCore;
        BallPen ballPen = new RedBallPen();
        penCore = ballPen.getPenCore();
        penCore.writeWord("wxynb");
        ballPen = new BlueBallPen();
        penCore = ballPen.getPenCore();
        penCore.writeWord("Hello World");
    }}abstract class PenCore{
    String color;
    public abstract void writeWord(String s);}class RedPenCore extends PenCore{

    public RedPenCore() {
        color = "红色";
    }

    @Override
    public void writeWord(String s) {
        System.out.println("写出" + color + "的字:" + s);
    }}class BluePenCore extends PenCore{
    public BluePenCore() {
        color = "蓝色";
    }
    @Override
    public void writeWord(String s) {
        System.out.println("写出" + color + "的字:" + s);
    }}class BlackPenCore extends PenCore{
    public BlackPenCore() {
        color = "黑色";
    }
    @Override
    public void writeWord(String s) {
        System.out.println("写出" + color + "的字:" + s);
    }}abstract class BallPen{
    BallPen(){
        System.out.println("生产了一只装有" + getPenCore().color + "笔尖的圆珠笔");
    }
    public abstract PenCore getPenCore();}class RedBallPen extends BallPen{
    @Override
    public PenCore getPenCore() {
        return new RedPenCore();
    }}class BlueBallPen extends BallPen{

    @Override
    public PenCore getPenCore() {
        return new BluePenCore();
    }}class BlackBallPen extends BallPen{

    @Override
    public PenCore getPenCore() {
        return new BluePenCore();
    }
}


其中BallPen类是构造者,ReadBallPen、BlueBallPen、BlackBallPen类是具体构造者角色,PenCore类是抽象产品角色,RedPenCore,BluePenCore、BlackPenCore类是具体产品角色

应用程序在使用工厂模式的时候,只和抽象产品、构造者以及具体构造这打交道,用户只需要了解抽象产品有哪些方法即可,不需要知道有哪些具体的产品

工厂方法的优点

  • 使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦

  • 工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需要知道该对象有哪些方法即可

适合工厂方法模式的场景

  • 用户需要一个类的子类的实例,但不希望与该类的子类形成耦合

  • 用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用

观察者模式

观察者模式的结构中包括四种角色

  • 主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法

  • 观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法

  • 具体主题(ConcreteSubject):具体主题时实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者

  • 具体观察者(ConcreteObserver),具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己称为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者

观察者模式的优点

  • 具体主题和具体观察者是松耦合关系。由于主题(Subject)接口仅仅依赖于观察者(Observer)接口,因此具体主题知识知道它的观察者是实现观察者(Observer)接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题(Subject)接口,因此具体观察者知识知道它依赖的主题是实现主题(Subject)接口的某个类的实例,但不需要知道具体是哪个类

  • 观察模式满足“开-闭原则”。主题(Subject)接口仅仅依赖于观察者(Observer)解耦,这样,就可以让创建具体主题的类也仅仅依赖于观察者(Observer)接口,因此如果增加新的实现观察者(Observer)接口的类,不必修改具有创建具体主题的类的代码,同样,创建具体观察者的类仅仅依赖于主题(Observer)接口,如果增加新的实现主题(Subject)接口的类,也不必修改创建具体观察者类的代码

适合观察者模式的场景

  • 当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合

  • 当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象那个需要更新数据

装饰器模式

装饰模式的结构中包括四种角色

  • 抽象组件(Component):抽象组件是一个抽象类。抽象组件定义了”被装饰者“需要进行”装饰“的方法

  • 具体组件(ConcreteComponent):具体组件是抽象组件的一个子类,具体组件嗯对实例称为”被装饰者“

  • 装饰(Decorator):装饰也是抽象组件的一个子类,但装饰还包含一个抽象组件声明的变量以保存”被装饰者“的引用。装饰可以是抽象类也可以是一个非抽象类,如果是非抽象类,那么该类实例称为”装饰者“

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

public class Main {

    public static void needBird(Bird bird){
        int flyDistance = bird.fly();
        System.out.println("这只鸟能飞行:"+ flyDistance);
    }

    public static void main(String[] args) {
        Sparrow sparrow = new Sparrow(); // 这只鸟能飞行:100
        needBird(sparrow);
        Bird sparrowDecorator1 = new SparrowDecorator(sparrow); //这只鸟能飞行:150
        Bird sparrowDecorator2 = new SparrowDecorator(sparrowDecorator1); //这只鸟能飞行:200
        needBird(sparrowDecorator1);
        needBird(sparrowDecorator2);
    }}abstract class Bird{
    public abstract int fly();}// 具体组件是Sparrowclass Sparrow extends Bird{

    @Override
    public int fly() {
        return 100;
    }}abstract class Decorator extends Bird{
    protected Bird bird;

    public Decorator() {
    }

    public Decorator(Bird bird) {
        this.bird = bird;
    }}class SparrowDecorator extends Decorator{
    public final int DISTANCE = 50;

    public SparrowDecorator() {
    }

    public SparrowDecorator(Bird bird) {
        super(bird);
    }

    @Override
    public int fly() {
        int distance = 0;
        distance = bird.fly() + eleFly();  // 委托被装饰着bird调用fly(),然后在调用eleFly()
        return distance;
    }

    // 设置private目的是使得客户程序只有调用Fly才能调用eleFly
    private int eleFly() {  // 装饰者新添加的方法
        return DISTANCE;
    }
}

装饰器模式的优点

  • 被装饰者和装饰者是松耦合关系。由于装饰(Decorator)仅仅依赖于抽象组件(Component),因此具体装饰只知道它要装饰的对象是抽象组件某一个子类的实例,但不需要知道是哪一个子类

  • 装饰模式满足”开-闭原则“。不必修改具体组件,就可以增加新的针对该具体组件的具体装饰

  • 可以使用多个具体装饰来装饰具体装饰的实例

适合装饰器模式的场景

  • 程序希望动态地增强类的某个对象的功能,而又不影响到该类的其他对象

  • 采用继承来增强对象功能不利于系统的扩展和维护

适配器模式

对象适配器模式的结构中包括三种角色

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

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

  • 适配器(Adapter):适配器是一个类,该类实现了目标接口并包含有被适配者的引用,即适配器的职责是对被适配者接口(抽象类)与目标接口进行适配

public class Main {
    public static void main(String[] args) {
        TreeElectricOutlet outlet;
        Wash wash = new Wash();
        outlet = wash;
        System.out.println("使用三相插座接通电流:");
        outlet.connectElectricCurrent();
        Tv tv = new Tv();
        TreeElectricAdapter adapter = new TreeElectricAdapter(tv);
        outlet = adapter;
        System.out.println("使用三相插座接通带电流:");
        outlet.connectElectricCurrent(); // 长江电视机开始播放节目
    }}// Targetinterface TreeElectricOutlet{
    public abstract void connectElectricCurrent();}//被适配者 ,就是两个插头的想用三个插头的通电interface TwoElectricOutlet{
    public abstract void connectElectricCurrent();}class TreeElectricAdapter implements TreeElectricOutlet{
    TwoElectricOutlet outlet;

    public TreeElectricAdapter(TwoElectricOutlet outlet) {
        this.outlet = outlet;
    }

    @Override
    public void connectElectricCurrent() {
        outlet.connectElectricCurrent();
    }}class Wash implements TreeElectricOutlet{
    private String name;

    public Wash() {
        this.name = "黄河洗衣机";
    }

    @Override
    public void connectElectricCurrent() {
        this.turnOn();
    }
    public void turnOn(){
        System.out.println(name+"开始洗衣物");
    }}class Tv implements TwoElectricOutlet{
    String name;
    Tv(){
        this.name = "长江电视机";
    }

    @Override
    public void connectElectricCurrent() {
        this.turnOn();
    }
    public void turnOn(){
        System.out.println(name+"开始播放节目");
    }}

适配器模式的优点

  • 目标(Target)与被适配者(Adaptee)是完全解耦的关系

  • 适配器模式满足“开闭原则”。当添加一个实现Adapter接口的新类时,不必修改Adapter,Adapter就能对这个新类的实例进行适配

适合适配器模式的场景

  • 一个程序想使用已经存在的类,但该类所实现的接口和当前程序所使用的接口不一致









博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。