首页 > 技术文章 > 设计模式(三)

wyzstudy 2021-11-01 19:11 原文

UML类图

UML基本介绍

  1. UML——Unified modeling language UML (统一建模语言),是一种用于软件系统 分析和设计的语言工具,它用于帮助软 件开发人员进行思考和记录思路的结果
  2. UML本身是一套符号的规定,就像数学 符号和化学符号一样,这些符号用于描 述软件模型中的各个元素和他们之间的 关系,比如类、接口、实现、泛化、依 赖、组合、聚合等
  3. 使用UML来建模,常用的工具有 Rational Rose , 也可以使用一些插件来建模

UML图

画UML图与写文章差不多,都是把自己的思想描述给别人看,关键在于思路和条理, UML图分类:

  • 用例图(use case)
  • 静态结构图:类图、对象图、包图、组件图、部署图
  • 动态行为图:交互图(时序图与协作图)、状态图、活动图

UML类图

  1. 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系。
  2. 类之间的关系:依赖、泛化(继承)、实现、关联、聚合与组合

依赖只要是在类中用到了对方,那么他们之间就存在依赖关系。如果没有对方,连编绎都通过不了。

  • 类中用到了对方
  • 如果是类的成员属性
  • 如果是方法的返回类型
  • 是方法接收的参数类型
  • 方法中使用到

泛化:泛化关系实际上就是继承关系,他是依赖关系的特例

实现:实现关系实际上就是A类实现B接口,他是依赖关系的特例

关联:关联关系实际上就是类与类之间的联系,他是依赖关系的特例,关联具有导航性:即双向关系或单向关系,关系具有多重性:如“1”(表示有且仅有一个),“0...”(表示0个或者多个), “0,1”(表示0个或者一个),“n...m”(表示n到 m个都可以),“m...*”(表示至少m 个)。

聚合:聚合关系(Aggregation)表示的是整体和部分的关系,整体与部分可以分开。聚 合关系是关联关系的特例,所以他具有关联的导航性与多重性。

组合:也是整体与部分的关系,但是整体与部分不可以分开。

设计模式概述

掌握设计模式的层次

第1层:刚开始学编程不久,听说过什么是设计模式

第2层:有很长时间的编程经验,自己写了很多代码,其中用到了设计模式,但是自己却不知道

第3层:学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式挺好用的

第4层:阅读了很多别人写的源码和框架,在其中看到别人设计模式,并且能够领会设计模式的精妙和带来的好处。

第5层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来。

设计模式介绍

设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern) 代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度

<<设计模式>>是经典的书,作者是Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides Design(俗称 “四人组 GOF”)

设计模式并不局限于某种语言,java,php,c++ 都有设计模式。

设计模式类型

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

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

创建型模式

单例模式

基本介绍

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

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

单例设计模式八种方式

单例模式有八种方式:

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

静态常量

class Singleton{
    //1.构造器私有化
    private Singleton(){}
    //2.提供静态属性
    private static final Singleton instance = new Singleton();
    //3.对外提供public返回静态实例
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点说明:

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

缺点:

1. 在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
2. 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载 的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果

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

静态代码块

class Singleton{
    //1.构造器私有化
    private Singleton(){}
    //2.提供静态属性
    private static final Singleton instance;
    static {
        instance = new Singleton();
    }
    //3.对外提供public返回静态实例
    public static Singleton getInstance(){
        return instance;
    }
}

优缺点说明:

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

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

懒汉式(线程不安全)

class Singleton{
    private Singleton(){};
    private static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();       
        }
        return instance;
    }
}

优缺点说明:

起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及 往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

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

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

class Singleton{
    private Singleton(){};
    private static Singleton instance;
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();       
        }
        return instance;
    }
}

优缺点说明:

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

结论:在实际开发中,不推荐使用这种方式

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

优缺点说明:

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

结论:在实际开发中,不能使用这种方式

双重检查

class Singleton{
    private Singleton(){};
    private static volatile Singleton instance;
    //双重检查
    public static synchronized Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

优缺点说明:

Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null), 直接return实例化对象,也避免的反复进行方法同步。 线程安全;延迟加载。效率较高

结论:在实际开发中,推荐使用这种单例设计模式

静态内部类

class Singleton{
    private Singleton(){};
    //私有静态内部类
    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }
    public Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

优缺点说明:

这种方式采用了类装载的机制来保证初始化实例时只有一个线程。 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化 时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的 实例化。 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。

结论:推荐使用。

枚举

enum Singleton{
    INSTANCE;
    public void say(){
        System.out.println("say");
    }
}

优缺点说明:

这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而 且还能防止反序列化重新创建新的对象。这种方式是Effective Java作者Josh Bloch 提倡的方式

结论:推荐使用

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

我们JDK中,java.lang.Runtime就是经典的单例模式(饿汉式)

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

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

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

工厂模式

简单工厂模式

看一个具体的需求,有一个披萨的项目:

  1. 要便于披萨种类的扩展,要便于维护
  2. 披萨的种类很多(比如 GreekPizz、CheesePizz 等)
  3. 披萨的制作有 prepare,bake, cut, box
  4. 完成披萨店订购功能。

传统方式实现

//定义一个披萨抽象类
public abstract class Pizza {
    private String name;

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

    public abstract void prepare();
    public void bake(){
        System.out.println(name + "烘烤");
    }
    public void cut(){
        System.out.println(name + "切割");
    }
    public void box(){
        System.out.println(name + "打包");
    }
}
//具体披萨类
public class CheesePizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("奶酪披萨准备原材料");
    }
}
public class GreekPizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("希腊披萨准备原材料");
    }
}
//订单类,依赖抽象披萨类、具体披萨类
public class PizzaOrder {
    public PizzaOrder(){
        do{
            Pizza pizza = null;
            String name = inputName();
            if(name.equals("cheese")){
                pizza = new CheesePizza();
                pizza.setName("奶酪披萨");
            }else if(name.equals("greek")){
                pizza = new GreekPizza();
                pizza.setName("希腊披萨");
            }else{
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }
	//获取披萨名字
    private String inputName(){
        Scanner scanner = new Scanner(System.in);
        System.out.println("input pizza name:");
        return scanner.next();
    }
}
//披萨店类
public class PizzaStore {
    public static void main(String[] args) {
        new PizzaOrder();
    }
}

传统的方式的优缺点:

优点是比较好理解,简单易操作。

缺点是违反了设计模式的ocp原则,即对扩展开放,对修改关闭。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。比如我们这时要新增加一个Pizza的种类(Pepper披萨),我们需要做如下修改。

简单工厂模式改进代码

//披萨订单类
public class PizzaOrder {
    //将SimpleFactory,Pizza聚合到披萨订单类内部
    private SimpleFactory simpleFactory;
    private Pizza pizza;

    public PizzaOrder(SimpleFactory simpleFactory){
        this.simpleFactory = simpleFactory;
        do{
            String name = inputName();
            pizza = this.simpleFactory.createPizza(name);
            if(pizza == null){
                System.out.println("订购披萨失败");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }
	//获取披萨名字
    private String inputName(){
        Scanner scanner = new Scanner(System.in);
        System.out.println("input pizza name:");
        return scanner.next();
    }
}
//简单工厂类
public class SimpleFactory {
    public Pizza createPizza(String name){
        Pizza pizza = null;
        System.out.println("使用简单工厂模式创建对象");
        if("cheese".equals(name)){
            pizza = new CheesePizza();
            pizza.setName("奶酪披萨");
        }else if("greek".equals(name)){
            pizza = new GreekPizza();
            pizza.setName("希腊披萨");
        }else if("paper".equals(name)){
            pizza = new PaperPizza();
            pizza.setName("胡椒披萨");
        }
        return pizza;
    }
}
//店铺类
new PizzaOrder(new SimpleFactory());
System.out.println("退出");

简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一 个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式

简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为。在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式。

工厂方法模式

看一个新的需求披萨项目新的需求:客户在点披萨时,可以点不同口味的披萨,比如北京的奶酪pizza、 北京的胡椒pizza 或者是伦敦的奶酪pizza、伦敦的胡椒pizza。

工厂方法模式

//抽象订单类
public abstract class OrderPizza {
    private String inputName(){
        System.out.println("input pizza name");
        Scanner scanner = new Scanner(System.in);
        return scanner.next();
    }

    public OrderPizza(){
        do{
            Pizza pizza = null;
            String name = inputName();
            pizza = createPizza(name);
            if(pizza == null){
                System.out.println("订购失败");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while(true);

    }
    public abstract Pizza createPizza(String name);
}
//不同口味的订单类
public class BJOrderPizza extends OrderPizza {
    @Override
    public Pizza createPizza(String name) {
        Pizza pizza = null;
        if("cheese".equals(name)){
            pizza = new BJCheesePizza();
        }else if("greek".equals(name)){
            pizza = new BJGreekPizza();
        }

        return pizza;
    }
}
public class LDOrderPizza extends OrderPizza {
    @Override
    public Pizza createPizza(String name) {
        Pizza pizza = null;
        if("cheese".equals(name)){
            pizza = new LDCheesePizza();
        }else if("greek".equals(name)){
            pizza = new LDGreekPizza();
        }
        return pizza;
    }
}

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

抽象工厂模式

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

//工厂接口
public interface AbsFactory {

    Pizza createPizza(String name);
}
//具体工厂
public class BJFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String name) {
        System.out.println("抽象工厂构建对象");
        Pizza pizza = null;
        if("cheese".equals(name)){
            pizza = new BJCheesePizza();
        }else if("greek".equals(name)){
            pizza = new BJGreekPizza();
        }
        return pizza;
    }
}
public class LDFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String name) {
        System.out.println("抽象工厂构造对象");
        Pizza pizza = null;
        if("cheese".equals(name)){
            pizza = new LDCheesePizza();
        }else if("greek".equals(name)){
            pizza = new LDGreekPizza();
        }
        return pizza;
    }
}
//订单类
public class OrderPizza {
    private AbsFactory factory;
    private Pizza pizza;
    public OrderPizza(AbsFactory absFactory){
        setFactory(absFactory);
    }
    public void setFactory(AbsFactory factory){
        this.factory = factory;
        do{
            String name = inputName();
            this.pizza = null;
            this.pizza = this.factory.createPizza(name);
            if(this.pizza == null){
                System.out.println("订购失败");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while(true);
    }
    private String inputName(){
        Scanner scanner = new Scanner(System.in);
        System.out.println("input pizza name:");
        return scanner.next();
    }
}

工厂模式在JDK源码中的使用

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

//Debug代码
Calendar cal = Calendar.getInstance();
// 注意月份下标从0开始,所以取月份要+1
System.out.println("年:" + cal.get(Calendar.YEAR));
System.out.println("月:" + (cal.get(Calendar.MONTH) + 1));
System.out.println("日:" + cal.get(Calendar.DAY_OF_MONTH));
System.out.println("时:" + cal.get(Calendar.HOUR_OF_DAY));
System.out.println("分:" + cal.get(Calendar.MINUTE));
System.out.println("秒:" + cal.get(Calendar.SECOND));

通过调用一个静态方法返回Calendar实例,继续追源码

通过我们的参数获取对象provider对象,如果获取成功,就创建对象返回,当没有获取provider对象的时候,就会使用简单工厂模式创建对象:

工厂模式总结

  1. 工厂模式的意义,将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦。从而提高项目的扩展和维护性。
  2. 三种工厂模式 (简单工厂模式、工厂方法模式、抽象工厂模式)
  3. 设计模式的依赖抽象原则
    • 创建对象实例时,不要直接new 类, 而是把这个new 类的动作放在一个工厂的方法中,并返回。有的书上说,变量不要直接持有具体类的引用。
    • 不要让类继承具体类,而是继承抽象类或者是实现interface(接口)
    • 不要覆盖基类中已经实现的方法。

推荐阅读