设计模式学习笔记(持续更新)

设计模式

设计模式的原则目的

设计模式是为了让程序有更好的:java

  1. 代码重用性(相同的代码,不用屡次编写)
  2. 可读性(编写规范性,便于其余程序员的阅读和理解)
  3. 可扩展性(当须要增长新的功能时候,很是的方便和容易)
  4. 可靠性(当增长新的功能时候,对原来的功能没有影响)
  5. 使程序呈现高内聚,低耦合的特性

设计模式七大设计原则

单一职责原则

基本介绍

对类来讲,即一个类只负责一项职责。如类A负责两个不一样职责:职责1和职责2。当职责1需求变动而改变A时,也有可能致使职责2执行错误,所以须要把类A的粒度分解为A1与A2。android

注意事项和细节
  1. 下降类的复杂度,一个类只负责一项职责。
  2. 提升类的可读性,可维护性。
  3. 下降变动引发的风险。
  4. 一般状况下,咱们应当遵循单一职责原则,只有逻辑足够简单,才能够在代码级违反单一职责原则;只有类中方法数量足够少,能够在方法级别保持单一职责原则。

接口隔离原则

基本介绍

客户端不该该依赖它不须要的接口,即一个类对另外一个类的依赖应该创建在最小的接口上。接口尽可能细化,同时接口中的方法尽可能少。ios

依赖倒置原则

基本介绍

经过抽象(接口或者抽象类)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。程序员

中心思想

面向接口编程算法

注意事项和细节
  1. 低层模块尽可能都要有抽象类或接口,或者二者都有,程序稳定性更好。
  2. 变量的声明类型尽可能是抽象类或接口,这样咱们的变量引用和实际对象间就存在一个缓冲层,利于程序的扩展和优化。
  3. 继承是遵循里式替换原则。

里式替换原则

基本介绍

父类能出现的地方子类就能够出现,并且替换成子类也不会出现任何错误或者异常,而使用者也无需知道父类仍是子类。数据库

注意事项和细节
  1. 在使用继承时,子类尽可能不要重写父类的方法。
  2. 里氏替换原则告诉咱们,继承实际上让两个类耦合性加强了,在适当的状况下,能够经过聚合、组合、依赖来解决问题。

开闭原则

基本介绍

软件实体(包括类、模块、功能等)应该对扩展开放,可是对修改关闭。编程

迪米特法则(最少知道法则)

基本介绍
  1. 一个类应该对本身须要耦合或调用的类知道得最少。
  2. 类与类的关系越密切,耦合度越大。
  3. 迪米特法则又称最少知道法则,即一个类对本身依赖的类知道的越少越好
  4. 迪米特法则还有个更简单的定义:只与直接的朋友通讯

UML类图

UML基本介绍

UML--Unified modeling language(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和思路的结果。 ####UML类图分类设计模式

  1. Dependency 表示依赖(使用)---->只要是在类中用到了对方,那么他们之间就存在依赖关系。若是A用到了B,代表A依赖B,即A——>B。
  2. Association 表示关联----->表示类与类之间的联系是依赖的特例,若是A用到了B,即表示A——>B。
  3. Generalization 表示泛化(继承)---->实际上就是继承关系,是依赖关系的特例。若是A继承B,即代表A——>B。
  4. Realization 表示实现---->A类实现B接口,是依赖关系的特例。若是A实现B,即代表A——>B。
  5. Aggregation 表示聚合----->表示总体和部分的关系,总体与部分能够分开是关联关系的特例
  6. Composite 表示组合----->表示总体和部分的关系,总体与部分不能够能够分开是关联关系的特例

设计模式概述

设计模式介绍

  1. 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design Pattern) 表明了最佳的实践。
  2. 设计模式的本质提升软件的维护性,通用性和扩展性,并下降软件的复杂度。

设计模式的类型

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

单例设计模式

单例设计模式介绍

所谓类的单例设计模式,就是采起必定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,而且该类只提供一个取得其对象实例的方法(静态方法)。安全

单例设计模式八种方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 双重检查
  6. 静态内部类
  7. 枚举

饿汉式(静态常量)

步骤
  1. 构造类私有化(防止外部调用产生新的对象)
  2. 类的内部建立对象
  3. 向外暴露一个静态公共方法。
代码
class Singleton {
    private Singleton() {

    }

    private static final Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }
}
复制代码
优缺点
优势

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

缺点

在类加载的时候就完成了实例化,没有达到懒加载的效果。若是从始至终都没有用过这个实例,则会形成内存的浪费。

结论

这种单例设计模式可用,可是可能会出现内存浪费。

饿汉式(静态代码块)

代码
class Singleton {
    private Singleton() {

    }
    private static Singleton singleton;
    static {
        singleton = new Singleton();
    }

    public static Singleton getInstance() {
        return singleton;
    }
}
复制代码
优缺点

与饿汉式(静态常量)的方式相似,只不过将类实例化的过程放在了静态代码块中,优缺点与饿汉式(静态常量)同样。

懒汉式(线程不安全)

代码
class Singleton {
    private Singleton() {

    }
    private static Singleton singleton;
    /** * 当调用的时候再建立对象,可是线程不安全。好比线程1执行到singleton为空,此时线程2也执行到此为止,而后就会产生两个对象。 * @return */
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
复制代码
优缺点
  1. 起到了懒加载(Lazy Loading)的效果,可是线程不安全,只能在单线程下使用。
  2. 若是在多线程下,一个线程进入了if (singleton == null)判断语句块,还将来得及往下执行,另外一个线程也经过了这个判断语句,这是便会产生多个实例。所以在多线程的环境下不可使用这个方式。

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

代码
class Singleton {
    private Singleton() {

    }
    private static Singleton singleton;

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
复制代码
优缺点
  1. 解决了线程安全问题
  2. 效率低下,每一个线程在获取类的实例的时候,都须要执行同步方法。
  3. 不推荐这种方法。

双重检查

代码
class Singleton {
    private Singleton() {

    }
    //为了不初始化操做的指令重排序
    private static volatile Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }

            }
        }
        return singleton;
    }
}
复制代码
优缺点
  1. Double-Check概念是多线程开发中常用到的,进行两次检查,就能够保证线程安全了。
  2. 实例代码只用执行一次,实现了懒加载。
  3. 线程安全,延迟加载,效率高。

静态内部类

代码
class Singleton {
    private Singleton() {

    }
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
复制代码
优缺点
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 当调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,JVM帮助咱们保证了线程的安全性。
  4. 推荐使用

枚举

代码
enum Singleton{
    INSTANCE;
}
复制代码
分析
  1. 枚举类实现其实省略了private的构造方法
  2. 枚举类的域(field)实际上是相应的enum类型的一个实例对象。

单例模式在JDK应用的源码分析

java.lang.Runtime就是经典的单例模式(饿汉式),代码以下:

private static Runtime currentRuntime = new Runtime();

    /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
复制代码

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

  1. 单例模式保证系统内存中该类只存在一个对象,节省了系统资源,对于一些须要频繁建立销毁的对象,使用单例模式能够提升系统性能。
  2. 单利模式使用的场景,须要频繁的进行建立和销毁的对象、建立对象时耗时过多或耗费资源过多,但又常常用到的对象、工具类对象、频繁访问数据库或文件的对象。

工厂模式

简单工厂模式

经过举个例子说明一下: 我喜欢养宠物,抽象一个宠物父类或者接口。

/** * 描述要养的宠物 * * @author bf * @date 2019/9/18 17:01 */
public interface Animal {
    void getAnimal();
}
复制代码

先养一个小狗吧:

public class Dog implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("养了一只小狗");
    }
}
复制代码

再养一只小猫:

public class Cat implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("养了一只小猫");
    }
}
复制代码

再养一只竹鼠:

public class Mouse implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("养一只竹鼠");
    }
}
复制代码

准备工做完成了,咱们去宠物馆(简单工厂类),宠物种类以下:

public class SimpleAnimalFactory {
    private static final int TYPE_DOG = 1;
    private static final int TYPE_CAT = 2;
    private static final int TYPE_MOUSE = 3;

    public static Animal createAnimals(int type) {
        switch (type) {
            case TYPE_DOG:
                return new Dog();
            case TYPE_CAT:
                return new Cat();
            case TYPE_MOUSE:
                return new Mouse();
            default:
                return null;
                
        }
    }

}
复制代码

简单宠物馆就提供三种动物,你说要什么,他就给你什么。这里我要了一只狗:

public static void main(String[] args) {
        Animal animals = SimpleAnimalFactory.createAnimals(1);
        animals.getAnimal();
    }
复制代码

输出以下:

养了一只小狗

特色
  1. 简单工厂模式是一个具体的类,并非接口抽象类。
  2. 因为createAnimals()方法是静态的,因此也称之为静态工厂
缺点
  1. 扩展性差(好比我要添加一个宠物的种类,还须要修改工厂类的方法)。

经过反射建立的简单工厂模式

使用反射实现简单工厂:

public static <T> T createAnimals(Class<T> clz) throws Exception {
        T result = null;
        result = (T) Class.forName(clz.getName()).newInstance();
        return result;
    }
复制代码

买宠物时调用:

Dog dog = SimpleReflectAnimalFactory.createAnimals(Dog.class);
 dog.getAnimal();
复制代码
优缺点
  1. 当须要添加宠物的种类时,不须要修改工厂类的代码
  2. Class.forName(clz.getName()).newInstance()调用的是无参构造方法,它和new对象是同样的性质,而工厂方法应该用于复杂对象的初始化,当须要调用有参的构造方法时便无能为力了。

工厂方法模式

工厂方法模式是简单工厂的进一步深化,在工厂方法模式中,咱们再也不提供一个统一的工厂类来建立全部的对象,而是针对不一样的对象提供不一样的工厂。也就是说每个对象都有一个与之对应的工厂

定义

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

咱们能够先经过实例来详细解释一下这个定义:

实例

依然使用养宠物的例子: 首先,编写一个宠物接口:

public interface Animal {
    void getAnimal();
}
复制代码

养狗的代码:

public class Dog implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("养了一只小狗");
    }
}
复制代码

养猫的代码:

public class Cat implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("养了一只小猫");
    }
}

复制代码

养竹鼠的代码:

public class Mouse implements Animal {
    @Override
    public void getAnimal() {
        System.out.println("养一只竹鼠");
    }
}
复制代码

如今咱们按照定义所说定义一个抽象的工厂接口GetAnimalFactory

public interface GetAnimalFactory {
    Animal getAnimal();
}

复制代码

Dog加载器工厂

public interface GetAnimalFactory {
    Animal getAnimal();
}
复制代码

Cat加载器工厂

public class GetCat implements GetAnimalFactory {
    @Override
    public Animal getAnimal() {
        return new Cat();
    }
}
复制代码

Mouse加载器工厂

public class GetMouse implements GetAnimalFactory {
    @Override
    public Animal getAnimal() {
        return new Mouse();
    }
}
复制代码

和简单工厂对比一下,最根本的区别在于简单工厂只有一个统一的工厂类,而工厂方法是针对每一个要建立的对象都会提供一个工厂类。

抽象工厂模式

定义

提供一个建立一系列相关或相互依赖对象的接口,而无需指定它们具体的类。(在抽象工厂模式中,每个具体工厂都提供了多个工厂方法用于产生多种不一样类型的对象) 抽象工厂能够划 分为4大部分:

  1. AbstractFactory(抽象工厂):声明了一组用于建立对象的方法,注意是一组。
  2. ConcreteFactory(具体工厂):它实现了抽象工厂中声明的建立对象的方法,生成一组具体对象。
  3. AbstractProduct(抽象产品):它为每种对象声明接口,在其中声明了对象所具备的业务方法。
  4. ConcreteProduct(具体产品):它定义具体工厂生产的具体对象。
实例

如今须要作一款跨平台的游戏,须要兼容Android和Ios两个移动操做系统,该游戏针对每一个系统都设计了一套操做控制器(OperationController)和界面控制器(UIController),下面经过抽象工厂方式完成这款游戏的架构设计。 先建立两个抽象产品接口。

抽象操做控制器

public interface OperationController {
    void control();
}
复制代码

抽象界面控制器

public interface UIController {
    void display();
}
复制代码

而后完成这两个系统平台的具体操做控制器和界面控制器

Android平台

public class AndroidOperationController implements OperationController {
    @Override
    public void control() {
        System.out.println("AndroidOperationController");
    }
}
复制代码
public class AndroidUIController implements UIController {
    @Override
    public void display() {
        System.out.println("AndroidUIController");
    }
}
复制代码

Ios

public class IosOperationController implements OperationController {
    @Override
    public void control() {
        System.out.println("IosOperationController");
    }
}

复制代码
public class IosUIController implements UIController {
    @Override
    public void display() {
        System.out.println("IosUIController");
    }
}

复制代码

下面定义一个抽象工厂,该工厂能够建立OperationController和UIController

public interface SystemFactory {
    OperationController createOperationController();
    UIController createUIController();
}
复制代码

而后在各平台具体的工厂类中完成操做控制器和界面控制器的建立过程

Android

public class AndroidFactory implements SystemFactory {
    @Override
    public OperationController createOperationController() {
        return new AndroidOperationController();
    }

    @Override
    public UIController createUIController() {
        return new AndroidUIController();
    }
}
复制代码

Ios

public class IOSFactory implements SystemFactory {
    @Override
    public OperationController createOperationController() {
        return new IosOperationController();
    }

    @Override
    public UIController createUIController() {
        return new IosUIController();
    }
}

复制代码

方法调用:

public static void main(String[] args) {
        //android
        AndroidFactory androidFactory = new AndroidFactory();
        OperationController androidOperationController = androidFactory.createOperationController();
        UIController androidUiController = androidFactory.createUIController();
        androidOperationController.control();
        androidUiController.display();

        //ios
        IOSFactory iosFactory = new IOSFactory();
        OperationController iosFactoryOperation = iosFactory.createOperationController();
        UIController iosUiController = iosFactory.createUIController();
        iosFactoryOperation.control();
        iosUiController.display();
    }
复制代码

针对不一样平台只经过建立不一样的工厂对象就完成了操做和UI控制器的建立。

使用场景
  1. 和工厂方法同样,客户端不须要知道他所建立的对象的类。
  2. 须要一组对象共同完成某种功能是,而且可能存在多组对象完成不一样功能的状况。
  3. 系统接口稳定,不会频繁的增长对象。由于一旦增长就须要修改原有代码,不符合开闭原则

模板方法模式(Template Method Pattern)

定义

定义一个操做中的算法的框架,而将一些步骤延迟到子类中。使得子类能够不改变一个算法的结构便可从新定义该算法的某些特定步骤。

举例

举个骑共享单车的例子,无论是骑哈罗单车仍是骑摩拜单车,咱们都要经历扫码解锁、骑车、上锁、支付这四个过程,所以能够设计成一个抽象方法。

public abstract class RideBike {
    /** * 扫码解锁 */
    public abstract void unlock();

    /** * 骑车 */
    public abstract void ride();

    /** * 上锁 */
    public abstract void lock();

    /** * 支付 */
    public abstract void pay();

    /** * 模拟使用共享单车 */
    public abstract void use();

}

复制代码

上班去了,开始去骑共享单车,发现了一辆哈罗单车,开始使用。

public class RideHelloBike extends RideBike {
    @Override
    public void unlock() {
        System.out.println("扫码解锁哈罗单车");
    }

    @Override
    public void ride() {
        System.out.println("开启骑哈罗单车");
    }

    @Override
    public void lock() {
        System.out.println("给哈罗单车上锁");
    }

    @Override
    public void pay() {
        System.out.println("支付哈罗单车的费用");
    }

    @Override
    public void use() {
        this.unlock();
        this.ride();
        this.ride();
        this.pay();
    }

}

复制代码

终于下班了,发现公司楼下的哈罗单车都被骑完了,那就骑摩拜单车吧。

public class RideMoBaiBike extends RideBike {
    @Override
    public void unlock() {
        System.out.println("扫码解锁摩拜单车");
    }

    @Override
    public void ride() {
        System.out.println("开启骑摩拜单车");
    }

    @Override
    public void lock() {
        System.out.println("给摩拜单车上锁");
    }

    @Override
    public void pay() {
        System.out.println("支付摩拜单车的费用");
    }

    @Override
    public void use() {
        this.unlock();
        this.ride();
        this.ride();
        this.pay();
    }

}

复制代码

骑摩拜和骑哈罗都用到了一样的方法,user()方法,代码重复了,这是病得治,药房就是使用模板方法模式。

模板方法模式相信你们都用过,就是抽象类里面的方法,不须要改变的方法。那么,下面咱们开始编写代码。

抽象类代码

public abstract class AbstractClass {
    protected abstract void unlock();
    protected abstract void ride();
    protected abstract void lock();
    protected abstract void pay();

    protected final void use() {
        this.unlock();
        this.ride();
        this.lock();
        this.pay();
    }

}
复制代码

这里说明一下:使用protected,只有同包下的父子类能够访问。 final修饰的方法代表子类不能重写父类方法。

实现类代码

public class ScanBicycle extends AbstractClass {
    @Override
    protected void unlock() {
        System.out.println("扫码解锁");
    }

    @Override
    protected void ride() {
        System.out.println("骑车");
    }

    @Override
    protected void lock() {
        System.out.println("上锁");
    }

    @Override
    protected void pay() {
        System.out.println("支付");
    }



    public static void main(String[] args) {
        ScanBicycle bicycle = new ScanBicycle();
        bicycle.use();
    }
}
复制代码

运行结果以下:

public class ScanBicycle extends AbstractClass {
    @Override
    protected void unlock() {
        System.out.println("扫码解锁哈罗单车");
    }

    @Override
    protected void ride() {
        System.out.println("骑哈罗车");
    }

    @Override
    protected void lock() {
        System.out.println("上哈罗锁");
    }

    @Override
    protected void pay() {
        System.out.println("支付哈罗费用");
    }
    
    public static void main(String[] args) {
        ScanBicycle bicycle = new ScanBicycle();
        bicycle.use();
    }
}

复制代码
优缺点及使用场景
优势
  1. 良好的封装性。把共有不变的方法封装在父类,而子类负责其余须要改变方法的实现逻辑。即封装不变部分,扩展可变部分
  2. 良好的扩展性。增长功能由子类实现基本方法拓展,符合单一职责原则和开闭原则。
  3. 复用代码。
缺点
  1. 因为是经过继承实现代码复用来改变算法,灵活度下降。
  2. 子类的执行影响父类的结果,增长代码的阅读难度。
使用场景
  1. 多个子类有公有方法,而且逻辑基本相同时。
  2. 重要、复杂的算法,能够把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
总结

模板方法看上去简单,可是整个模式涉及到的都是面向对象设计的核心,好比继承封装、基于继承代码的复用、方法实现等等。当中还有涉及到一些关键词的使用,也是咱们在Java编程中须要掌握的基础。

建造者模式

相关文章
相关标签/搜索