Java设计模式总结

设计模式的七大原则

  1. 单一职责原则
  2. 接口隔离原则
  3. 依赖倒转原则
  4. 里氏替换原则
  5. 开闭原则
  6. 迪米特法则
  7. 合成复用原则

设计模式的目的

编写软件过程中,程序员面临着来自耦合性内聚性以及可维护性可扩展性重用性灵活性等多方面的挑战,设计模式是为了让程序(软件),具更好

  1. 代码重用性(即:相同功能的代码,不用多次编写)
  2. 可读性(即:编程规范,便于其他程序员的阅读和理解)
  3. 可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护)
  4. 可靠性(即:当我们增加新的功能后,对原来的功能没有影响)
  5. 使程序呈现高内聚低耦合的特性

单一职责原则

基本介绍

对类来说的,即一个类应该值负责一项职责,如类A负责两个不同职责,职责1,职责2,当职责1需求变更为改变A时,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2

单一职责原则注意事项和细节

  1. 降低类的复杂度,一个类只负责一项职责。
  2. 提高类的可读性,可维护性。
  3. 降低变更引起的风险。
  4. 通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

接口隔离原则

基本介绍

客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应建立在最小接口上

image-20210710105234755

类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口Interface1对于类A和类C来说不是最小接口那么类B和类D必须去实现他们不需要的方法

按隔离原则应当这样处理:

将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关西。也就是采用接口隔离原则。

依赖倒转原则

基本介绍

依赖倒转原则(Dependence Inversion Principle)是指:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象
  3. 依赖倒转的中心思想时面向接口编程
  4. 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或抽象类,细节就是具体的实现类。
  5. 使用接口或抽象类的目的是指定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

依赖关系传递的三种方式和应用案例

  1. 接口传递
  2. 构造方法传递
  3. setter方式传递

依赖倒转原则的注意事项和细节

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

里氏替换原则

oo中的继承性的思考和说明

  1. 继承包含这样一个含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏。
  2. 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被其他的类所继承。则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障。
  3. 问题提出:在编程中,如何正确的使用继承?=》里氏替换原则

基本介绍

  1. 里氏替换原则(Liskov Substitution Principle)在1998年,由麻省理工学院的一位姓里的女士提出的。
  2. 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2时类型T1的子类型,换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
  3. 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  4. 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题。

解决方法

原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系替代。

开闭原则

基本介绍

  1. 开闭原则(Open Closed Principle)时编程中最基础、最重要的设计原则
  2. 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化
  4. 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。

迪米特法则

基本介绍

  1. 一个对象应该对其他对象保持最少的了解
  2. 类与类关系越密切,耦合度越大
  3. 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供public方法,不对外泄露任何信息。
  4. 迪米特法则还有个更简单的定义:只与直接的朋友通信
  5. 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

迪米特法则注意事项和细节

  1. 迪米特法则的核心是降低类之间的耦合
  2. 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

合成复用原则

基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承

image-20210710183317994

设计原则的思想

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  2. 针对接口编程,而不是针对实现编程。
  3. 为了交互对象之间的松耦合设计而努力。

UML类图

UML基本介绍

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

  2. UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等。

    image-20210710184108246

  3. 使用UML来建模,常用的工具有Rational Rose,也可以使用一些插件来建模

设计模式概述

设计模式介绍

  1. 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
  2. 设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度。
  3. 设计模式并不局限于某种语言,java,php,c++都有设计模式。

设计模式类型

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

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

注意:不同的书籍上对分类和名称略有差别。

设计模式

单例模式

单例设计模式介绍

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

比如Hibernate和Session Factory,它充当数据存储源的代理,并负责创建Session对象。Session Factory并不是轻量级的,一般情况下,一个项目通常只需要一个Session Factory就够,这时就会使用到单例模式。

单例设计模式八种方式

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

饿汉式(静态常量)

步骤如下
  1. 构造器私有化(防止new)
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法。getInstance
  4. 代码实现
//饿汉式(静态变量)
class Singleton{

    //1.构造器私有化,外部能new
    private Singleton(){
    }

    //2.本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    //3。提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}
优缺点
  1. 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
  2. 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
  3. 这种方式基于class loder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到Lazy Loading的效果。
  4. 结论:这种单例模式可用,可能造成内存浪费。

饿汉式(静态代码块)

代码演示
class Singleton{
    //1.构造器私有化,外部能new
    private Singleton(){
        
    }

    static {//在静态代码块中创建单例对象
        instance = new Singleton();
    }
    //2.本类内部创建对象实例
    private static Singleton instance;

    //3。提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}
优缺点说明
  1. 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优缺点和上面时一样的。
  2. 结论:这种单例模式可用,但是可能造成内存浪费。

懒汉式(线程不安全)

代码演示
class Singleton{
    private static Singleton instance;
    
    private Singleton(){}
    
    //提供一个静态的公有方法,当使用到该方法时,菜去创建instance
    //即懒汉式
    public static Singleton getInstance(){
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
优缺点说明
  1. 起到了Lazy Loading的效果,但是只能在单线程下使用。
  2. 如果在多线程下,一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
  3. 结论:在实际开发中,不要使用这种方式。

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

代码实现
class Singleton{
    private static Singleton instance;

    private Singleton(){}

    //加入了同步代码,解决线程安全问题
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}
优缺点说明
  1. 解决了线程不安全问题。
  2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。
  3. 结论:在实际开发中,不推荐使用这种方式。

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

优缺点说明
  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的代码块。
  2. 但是这种同步并不能起到线程同步的作用。跟第三种实现方式遇到的情形一致,假如一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
  3. 结论:在实际开发中,不能使用这种方式

双重检查

代码演示
class Singleton{
    
    //当instance改变时,立马将instance的值写入主存
    private static volatile Singleton instance;

    private Singleton(){}
    
    //提供一个静态的公有方法,加入双重检查代码,解决线程安全的安全问题,同时解决懒加载问题
    //同时保证了效率
    public static Singleton getInstance(){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
优缺点说明
  1. Double-Check概念时多线程开发中常使用到的,如代码中所示,我们进行了两次if(instance == null)检查,这样就可以保证线程安全了。
  2. 这样,实例化代码只用执行一次,后面再次访问时,判断if(instance == null),直接return实例化对象,也避免的反复进行方法同步。
  3. 线程安全;延迟加载;效率较高
  4. 结论:在实际开发中,推荐使用这种单例设计模式

静态内部类

静态内部类的特点
  1. 当外部类被装载时,静态内部类并不会被立即装载
  2. 当使用静态内部类的成员时,静态内部类会被装载,并且指挥被转载一次,并且在装载时线程是安全的
代码演示
class Singleton{
    
    
    //构造器私有化
    private Singleton(){}
    
    //写一个静态内部类,该类中有一个静态属性Singleton
    private static class SingletonInstance{
        //JVM低层装载机制
        private static final Singleton INSTANCE = new Singleton();
    }
    
    //提供一个静态公有方法,直接返回SingletonInstance.INSTANCE
    public static synchronized Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
    
}
优缺点说明
  1. 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  2. 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
  3. 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程时无法进入的。
  4. 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
  5. 结论:推荐使用

枚举

代码演示
//使用枚举,可以实现单例
enum Singleton{
    INSTANCE;  //属性
    public void sauOK(){
        System.out.println("ok~");
    }
}
优缺点说明
  1. 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
  2. 这种方式时Effective Java作者Josh Bloch提倡的方式
  3. 结论:推荐使用

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

  1. 我们JDK中,java.lang.Runtime就是经典的单例模式
  2. 代码分析+Debug源码+代码说明

饿汉式

image-20210712085549020

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

  1. 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  3. 单例模式使用的场景:需要频繁的进行创建和销毁对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

简单工厂设计模式

需求

要便于披萨种类的扩展,要便于维护

  1. 披萨的种类很多(比如 GeekPizz、CheesePizz等)
  2. 披萨的制作有prepare,bake,cut,box
  3. 完成披萨店订购功能

不使用工厂模式进行编写

Pizza.java

//将Pizza类做成抽象
public abstract class Pizza {

    protected String name; //名字

    //准备原材料,不同的披萨不一样,因此,我们做成抽象方法
    public abstract void prepare();

    public void bake(){
        System.out.println(name+"baking");
    }

    public void cut(){
        System.out.println(name+"cutting");
    }

    public void box(){
        System.out.println(name+"boxing");
    }

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

Greek.java

public class GreekPizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("给希腊披萨准备原材料");
    }
}

Cheese.java

public class CheesePizza extends Pizza{
    @Override
    public void prepare() {
        System.out.println("给制作奶酪披萨准备原材料");
    }
}

OrderPizza.java

public class OrderPizza {

    //构造器
    public OrderPizza() {
        Pizza pizza = null;
        String orderType; //订购披萨的类型
        do {
            orderType = gettype();
            if (orderType.equals("greek")){
                pizza = new GreekPizza();
                pizza.setName("希腊披萨");
            }else if (orderType.equals("cheese")){
                pizza = new CheesePizza();
                pizza.setName("奶酪披萨");
            }else{
                break;
            }
            //输出披萨的制作过程
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }



    //写一个方法,可以获取客户希望订购的披萨种类
    private String gettype(){
        try{
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("inout pizza type:");
            String str = strin.readLine();
            return  str;
        }catch (IOException e){
            e.printStackTrace();
            return "";
        }
    }

}

PizzaStore.java

//相当于一个客户端,发出订购任务
public class PizzaStore {
    public static void main(String[] args) {
        new OrderPizza();
    }
}

传统方式的优缺点

  1. 优点是比较好理解,简单易操作。
  2. 缺点是违反了设计模式的ocp原则,即对扩展开发,对修改关闭。即当我们给类增加新功能的时候,尽量不要修稿代码,或者尽可能少修改代码。
  3. 比如我们这时要新增一个Pizza的种类(Cheese披萨),我们需要做许多修改。

改进的思路分析

分析:修改代码可以接受,但是如果我们在其他地方也有创建Pizza的代码,就意味着,也需要修改,而创建Pizza的代码,往往有多出。

思路:把创建Pizza对象封装到一个类中,这样我们有新的Pizza种类时,只需要修改该类就可,其他有创建到Pizza对象的代码就不需要修改了。->简单工厂模式。

简单工厂模式(静态工厂模式)基本介绍

  1. 简单工厂模式时属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
  2. 简单工厂模式:定义一个创建对象的类,由这个类类封装实例化对象的行为(代码)
  3. 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式。

代码演示

SimpleFactory.java

//简单工厂类
public class SimpleFactory {


    //根据orderType返回对应的Pizza对象
    public Pizza createPizza(String orderType){
        Pizza pizza = null;
        System.out.println("使用简单工厂模式");
        if (orderType.equals("greek")){
            pizza = new GreekPizza();
            pizza.setName("希腊披萨");
        }else if (orderType.equals("cheese")){
            pizza = new CheesePizza();
            pizza.setName("奶酪披萨");
        }
        return pizza;
    }
}

OrderPizza.java

public class OrderPizza {

    //构造器
    /*public OrderPizza() {
        Pizza pizza = null;
        String orderType; //订购披萨的类型
        do {
            orderType = gettype();
            if (orderType.equals("greek")){
                pizza = new GreekPizza();
                pizza.setName("希腊披萨");
            }else if (orderType.equals("cheese")){
                pizza = new CheesePizza();
                pizza.setName("奶酪披萨");
            }else{
                break;
            }
            //输出披萨的制作过程
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }*/

    //定义一个简单工厂对象
    SimpleFactory simpleFactory;

    //构造器


    public OrderPizza(SimpleFactory simpleFactory) {
        setSimpleFactory(simpleFactory);
    }

    Pizza pizza = null;
    public OrderPizza setSimpleFactory(SimpleFactory simpleFactory) {
        String orderType = ""; //用户输入

        this.simpleFactory = simpleFactory;  //设置一个简单工厂对象


        do {
            orderType= gettype();
            pizza = simpleFactory.createPizza(orderType);
            //输出披萨的信息
            if (pizza!=null){
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else{
                System.out.println("订购披萨失败");
                break;
            }
        }while (true);
        return this;
    }

    //写一个方法,可以获取客户希望订购的披萨种类
    private String gettype(){
        try{
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("inout pizza type:");
            String str = strin.readLine();
            return  str;
        }catch (IOException e){
            e.printStackTrace();
            return "";
        }
    }

}

工厂方法模式

基本介绍

工厂方法模式设计方案:将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现。

工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。

抽线工厂模式

基本介绍

  1. 抽象工厂模式:定义了一个interface用于创建相关或有依赖的对象簇,而无需指明具体的类。
  2. 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
  3. 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
  4. 将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

image-20210712143159726

工厂模式在JDK-Calendar应用的源码分析

image-20210712143510235

原型模式

克隆羊问题

现在有一只羊tom,姓名为:Tom,年龄为:1,颜色为:白色,请编写程序创建和Tom羊属性完全相同的10只羊。

传统方法

Sheep.java

public class Sheep {

    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public Sheep setAge(int age) {
        this.age = age;
        return this;
    }

    public String getColor() {
        return color;
    }

    public Sheep setColor(String color) {
        this.color = color;
        return this;
    }
}

通过new来创建羊

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("Tom",1,"白色");

    }
}

传统方式的优缺点

  1. 优点是比较好理解,简单易操作。
  2. 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低。
  3. 总是需要重新初始化对象,而不是动态地获得对象运行时地状态,不够灵活
  4. 改进地思路分析

思路:Java中Object类时所有类地根类,Object类提供了一个clone()方法,该方法可以将一个Java对象复制一份,但是需要实现clone地Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力=》原型模式

原型模式-基本介绍

基本介绍

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  2. 原型模式时一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动船创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象clone()
  4. 形象的理解:孙大圣拔出猴毛,变出其他孙大圣

image-20210712151443153

原型模式再Spring框架中源码分析

  1. Spring中原型bean的创建,就是原型模式的应用

深入讨论-浅拷贝和深拷贝

浅拷贝的介绍
  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性复制一份给新的对象。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
  3. 前面我们克隆羊就是浅拷贝
  4. 浅拷贝是使用默认的clone()方法实现
深拷贝基本介绍
  1. 复制对象的所有基本数据类型的成员变量值。
  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝
  3. 深拷贝实现方式1:重写clone方法来实现深拷贝
  4. 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐),有利于后续代码的扩展和维护
public class DeepProtoType implements Serializable,Cloneable {
    public String name;  //String属性
    public DeepCloneableTarget deepCloneableTarget; //引用类型
    public DeepProtoType(){
        super();
    }

    //深拷贝 - 方式1使用clone方法

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object deep = null;
        //这里完成对基本数据类型(属性)和String的克隆
        deep = super.clone();
        //对引用类型的属性,进行单独处理
        DeepProtoType deepProtoType = (DeepProtoType) deep;
        deepProtoType.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();

        return deepProtoType;
    }


    //深拷贝 - 方式2通过对象的序列化实现(推荐)
    public Object deepClone(){
        //创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try{
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);//当前这个对象以对象流的方式输出

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            DeepProtoType copyObj = (DeepProtoType) ois.readObject();

            return copyObj;

        }catch (Exception e){
            e.printStackTrace();
            return null;
        }finally {
            try{
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

建造者模式

盖房项目需求

  1. 需要建房子:这一过程为打桩、砌墙、封顶
  2. 房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是需求不要相同的
  3. 请编写程序,完成需求

传统方式

代码演示

AbstractHouse.java

public abstract class AbstractHouse {
    //打地基
    public abstract void buildBasic();
    //砌墙
    public abstract void buildWalls();
    //封顶
    public abstract void roofed();

    public void build(){
        buildBasic();
        buildWalls();
        roofed();
    }
}

CommonHouse.java

public class CommonHouse extends AbstractHouse{
    @Override
    public void buildBasic() {
        System.out.println("普通房子打地基");
    }

    @Override
    public void buildWalls() {
        System.out.println("普通房子砌墙");
    }

    @Override
    public void roofed() {
        System.out.println("普通房子封顶");
    }
}

Client.java

public class Client {
    public static void main(String[] args) {
        new CommonHouse().build();
    }
}

传统方式解决盖房子需求问题分析

  1. 优点是比较好理解,简单易操作
  2. 设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好。也就是说,这种设计方案,把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了。
  3. 解决方案:将产品和产品建造过程解耦=》建造者模式

建造者模式

基本介绍

  1. 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
  2. 建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建他们,用户不需要知道内部的具体构建细节。

建造者模式的四个角色

  1. Product(产品角色):一个具体的产品对象。
  2. Builder(抽象建造者):创建一个Product对象的各个部件指定的接口。
  3. Concrete Builder(具体建造者):实现接口,构建和装配各个部件。
  4. Director(指挥者):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

House.java

public class House {
    private String baise;
    private String wall;
    private String roofed;



    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof House)) return false;

        House house = (House) o;

        if (getBaise() != null ? !getBaise().equals(house.getBaise()) : house.getBaise() != null) return false;
        if (getWall() != null ? !getWall().equals(house.getWall()) : house.getWall() != null) return false;
        return getRoofed() != null ? getRoofed().equals(house.getRoofed()) : house.getRoofed() == null;
    }

    @Override
    public int hashCode() {
        int result = getBaise() != null ? getBaise().hashCode() : 0;
        result = 31 * result + (getWall() != null ? getWall().hashCode() : 0);
        result = 31 * result + (getRoofed() != null ? getRoofed().hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "House{" +
                "baise='" + baise + '\'' +
                ", wall='" + wall + '\'' +
                ", roofed='" + roofed + '\'' +
                '}';
    }

    public String getBaise() {
        return baise;
    }

    public House setBaise(String baise) {
        this.baise = baise;
        return this;
    }

    public String getWall() {
        return wall;
    }

    public House setWall(String wall) {
        this.wall = wall;
        return this;
    }

    public String getRoofed() {
        return roofed;
    }

    public House setRoofed(String roofed) {
        this.roofed = roofed;
        return this;
    }
}

HouseBuilder.java

//抽象的建造者
public abstract class HouseBuilder {
    protected House house = new House();


    //将建造的流程写好,抽象的方法
    public abstract void buildBasic();
    public abstract void buildWalls();
    public abstract void roofed();

    //建造房子好,将产品(房子)返回
    public House buildHouse(){
        return house;
    }
}

CommonHouse.java

public class CommonHouse extends HouseBuilder{
    @Override
    public void buildBasic() {
        System.out.println("普通房子打地基5米");
    }

    @Override
    public void buildWalls() {
        System.out.println("普通房子砌墙10cm");
    }

    @Override
    public void roofed() {
        System.out.println("普通房子盖屋顶");
    }
}

HighBuilding.java

public class HighBuilding extends HouseBuilder{
    @Override
    public void buildBasic() {
        System.out.println("高楼打地基30米");
    }

    @Override
    public void buildWalls() {
        System.out.println("高楼砌墙20cm");
    }

    @Override
    public void roofed() {
        System.out.println("高楼透明屋顶");
    }
}

HouseDirector.java

//指挥者,这里去指定制作流程,放回产品
public class HouseDirector {
    HouseBuilder houseBuilder = null;

    public HouseDirector(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    public HouseBuilder getHouseBuilder() {
        return houseBuilder;
    }

    public HouseDirector setHouseBuilder(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
        return this;
    }

    //如何处理建造房子的流程,交给指挥者
    public House constructHouse(){
        houseBuilder.buildBasic();
        houseBuilder.buildWalls();
        houseBuilder.roofed();
        return houseBuilder.buildHouse();
    }
}

Client.java

public class Client {

    public static void main(String[] args) {
        //盖普通房子
        CommonHouse commonHouse = new CommonHouse();


        //准备创建房子的指挥者
        HouseDirector houseDirector = new HouseDirector(commonHouse);

        House house = houseDirector.constructHouse();
    }

}

建造者模式在JDK的应用和源码分析

java.lang.StringBuilder中的建造者模式

抽象建造者:

Appendable接口定义了多个append方法(抽象方法),即Appendable为抽象建造者,定义了抽象方法

image-20210712184227317

建造者:

AbstractStringBuilder实现了Appendable接口方法,这里的AbstractStringBuilder已经是建造者,只是不能实例化

image-20210712184554285

指挥者:

StringBuilder即充当了指挥者角色,同时充当了具体的建造者,建造方法的实现是由AbstractStringBuilder完成,而StringBuilder继承了AbstractStringBuilder

image-20210712184728957

观点:分析源码的时候,和标准的设计模式不一样,因为作者在编写源码的时候,设计模式可能还未提出,或者作者有别的实现方式,所以不一定和设计模式一样,但是精髓还是一样的

建造者模式的注意事项和细节

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象

  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新地具体建造者,用户使用不同地具体建造者即可得到不同地产品对象

  3. 可以更加精细地控制产品地创建过程。将复杂产品地创建步骤分解在不同地方法中,使得创建过程更加清晰也更方便使用程序来控制创建过程。

  4. 增加新地具体创建者无需修改原有类库的代码,指挥者类针对抽象创建者类编程,系统扩展方便,符合”开闭原则“。

  5. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制

  6. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式

  7. 抽象工厂模式VS建造者模式

    抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新的产品。

设配器模式

泰国旅游使用插座问题

现实生活中的适配器的例子

泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了。

适配器模式

基本介绍

  1. 适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
  2. 适配器模式属于结构型模式
  3. 主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口。让原本接口不兼容的类可以兼容。
  2. 从用户的角度看不到被是被适配者,是解耦的。
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法。
  4. 用户收到反馈结果,感觉只是和目标接口交互,如图:
image-20210712201753134

类适配器模式

基本介绍

Adapter类,通过继承src类,实现dst类接口,完成src->dst的适配

类适配器模式注意事项和细节
  1. Java是单继承机制,所以类适配器需要继承src类这一点算是一个缺点,因为这要求dst必须是接口,有一定局限性。
  2. src类的方法再Adapter中都会暴露出来,也增加了使用成本。
  3. 由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

对象适配器模式

基本介绍
  1. 基本思想和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即:持有src类,实现dst类接口,完成src->dst的适配。
  2. 根据"合成复用原则",再系统中尽量使用关联关系来替代继承关系。
  3. 对象适配器模式是适配器模式常用的一种。

image-20210713101609401

对象适配器模式注意事项和细节
  1. 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承src的局限性问题,也不再要求dst必须是接口。
  2. 使用成本更低,更灵活。

接口适配器模式

基本介绍
  1. 一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式。
  2. 当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每一个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类地某些方法来实现需求。
  3. 适用于一个接口不想使用其所有的方法的情况。

image-20210713103230908

适配器模式在SpringMVC框架应用的源码分析

SpringMVC中的HandlerAdapter就使用了适配器模式

使用HandlerAdapter的原因分析:

可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来得代码,这样违背了OCP原则。

image-20210713104002826

image-20210713104205096

适配器模式得注意事项和细节

  1. 三种命名方式,是根据src是以怎样得形式给到Adapter(在Adapter里的形式)来命名的。

  2. 类适配器:以类给到,在Adapter里,就是将src当作类,继承

    对象适配器:以对象给到,在Adapter里,将src作为一个对象,持有

    接口适配器:以接口给到,在Adapter里,将src作为一个接口,实现

  3. Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作

  4. 实际开发中,实现起来不拘泥于我们讲解的三种经典形式

桥接模式

手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网、打电话等),如图:

image-20210713105659450

传统方案

image-20210713135841990

传统方案解决手机操作问题分析

  1. 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
  2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。
  3. 解决方案-使用桥接模式

桥接模式

基本介绍

  1. 桥接模式(Bridge模式)是指:将实现与抽象放在两个不同的类层次中,使得两个层次可以独立改变。
  2. 是一种结构型设计模式。
  3. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。

image-20210713141256416

桥接模式在JDBC的源码剖析

JDBC的Driver接口,如果从桥接模式来看,Driver就是一个接口,下面可以有MySQL的Driver,Oracle的Driver,这些就可以当作实现接口类

image-20210713142204789

image-20210713142848740

桥接模式的注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其他的部分由具体业务来完成。
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性,即需要有这样的应用场景。

桥接模式其他应用场景

  1. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

  2. 常见的应用场景

    JDBC驱动程序

    银行转账系统

    转账分类:网上转账、柜台转账、AMT转账

    装转用户类型:普通用户、银行卡用户、金卡用户、

    消息管理

    消息类型:即时消息、延时消息

    消息分类:手机短信、邮件消息、QQ消息

装饰着设计模式

星巴克咖啡订单项目

  1. 咖啡种类/单品咖啡:Espresso(意大利浓咖啡)、ShortBlack、LongBlack(美式咖啡)、Decaf(无因咖啡)
  2. 调料:Milk、Soy、Chocolate
  3. 要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
  4. 使用OO来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以单品咖啡+调料组合

image-20210713144439703

image-20210713144656208

装饰着模式

定义

  1. 装饰着模式:动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰着模式也体现了开闭原则(OCP)

image-20210713145940426


标题:Java设计模式总结
作者:linrty
地址:https://blog.linrty.top/articles/2021/07/10/1697095655186.html

    评论
    0 评论
avatar

取消