William的总结(十三):23种设计模式分析(1-10)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_21918021/article/details/88655872

一、简单工厂模式(重要)(创建型)

简单工厂模式概述

简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例,简单来说就是,通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

首先举个例子:创建两个类,一个Apple,一个Banana,都有个一方法

 public class Apple{
     public void get(){
         System.out.println("采集苹果");
     }
 }
 public class Banana{
     public void get(){
         System.out.println("采集香蕉");
     }
 }

在创建一个主方法来调用这两个类中的方法

public class MainClass {
    public static void main(String[] args) {
        //实例化Apple
        Apple apple = new Apple();
        //实例化Banana
        Banana banana = new Banana();
        
        apple.get();
        banana.get();
    }
}

执行后,输出结果为:

  • 采集苹果
  • 采集香蕉

但是代码还可以改进,可以看出Apple与Banana的功能类似,所以可以抽取出一个接口,并让Apple与Banana实现此接口

 public interface Fruit {
     public void get();
 }
 
 public class Apple implements Fruit{
     public void get(){
         System.out.println("采集苹果");
     }
 }
 public class Banana implements Fruit{
     public void get(){
         System.out.println("采集香蕉");
     }
 }

这时,主方法调用形式就变成了这样。

public class MainClass {
    public static void main(String[] args) {
        //实例化一个Apple,用到了多态
        Fruit apple = new Apple();
        //实例化一个Banana,用到了多态
        Fruit banana= new Banana();
        
        apple.get();
        banana.get();
    }
}

这样还是刚才的结果,不同的代码达到了相同的目的。

而简单工厂模式则是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类,上面的代码可以看出Apple与Banana实现了同一个接口,所以我们还需要创建一个工厂类来专门创建Apple与Banana的实例,继续改进。

创建一个工厂类

public class FruitFactory {
    //获取Apple的实例,用static修饰,方便使用
    public static Fruit getApple(){
        return new Apple();
    }
    
    //获取Banana的实例,用static修饰,方便使用
    public static Fruit getBanana(){
        return new Banana();
    }
}

Apple类,Banana类与Fruit接口没有变化。

主方法修改为

public class MainClass {
    public static void main(String[] args) {
        //实例化一个Apple,用到了工厂类
        Fruit apple = FruitFactory.getApple();
        //实例化一个Banana,用到了工厂类
        Fruit banana= FruitFactory.getBanana();
        
        apple.get();
        banana.get();
    }
}

同样和之前是一样的结果

这就是一个简单工厂模式的基本使用了,但这样的工厂类还不够好,例子中只有两个实例对象,但如果例子多了以后,工厂类就会产生很多很多的get方法。所以进行如下优化

public class FruitFactory {
    public static Fruit getFruit(String type) throws InstantiationException, IllegalAccessException{
        //不区分大小写
        if(type.equalsIgnoreCase("Apple")){
            return Apple.class.newInstance();
        }else if(type.equalsIgnoreCase("Banana")){
            return Banana.class.newInstance();
        }else{
            System.out.println("找不到相应的实体类");
            return null;
        }
    }
}

这时主方法修改为:

public class MainClass {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        //实例化一个Apple,用到了工厂类
        Fruit apple = FruitFactory.getFruit("apple");
        //实例化一个Banana,用到了工厂类
        Fruit banana= FruitFactory.getFruit("banana");
        apple.get();
        banana.get();
    }
}

这样就可以根据传入的参数动态的创建实例对象,而且传入的参数还可以自定义,非常的灵活,但缺点也很明显,工厂类中有大量的判断。

还有另外一种方式

public class FruitFactory {
    public static Fruit getFruit(String type) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
        Class fruit = Class.forName(type);
        return (Fruit) fruit.newInstance();
    }
}

主方法修改为

public class MainClass {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //实例化一个Apple,用到了工厂类
        Fruit apple = FruitFactory.getFruit("Apple");
        //实例化一个Banana,用到了工厂类
        Fruit banana= FruitFactory.getFruit("Banana");
        apple.get();
        banana.get();
    }
}

这种方法可以看到,工厂类非常的简洁,但主方法在调用时,输入的参数就固定了,必须为实例类名,不像上一种方法那么灵活。

这两种方法各有各的优点,可根据实际情况自己选择。

简单工厂模式中包含的角色及其职责

  1. 工厂(Creater)角色

简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象(FruitFactory类)

  1. 抽象(Product)角色

简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。(Fruit接口)

  1. 具体产品(Concrete Product)角色

简单工厂模式所创建的具体实例对象。(Apple类与Banana类)

简单设计模式的优缺点

  • 优点:简单工厂模式中,工厂类是整个模式的关键所在。它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。用户在创建时可以直接使用工厂类去创建所需的实例,而无需去了解这些对象是如何创建以及如何组织的,明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
  • 缺点:很明显简单工厂模式的缺点也体现在其工厂类上,由于工厂类集中了所有实例的创建逻辑,容易违反GRASPR的高内聚的责任分配原则,另外,当系统中的具体产品类不断增多时,可能会出现要求更改相应工厂类的情况,拓展性并不是很好。

使用场景

  • 工厂类负责创建的对象比较少;
  • 客户只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心;
  • 由于简单工厂很容易违反高内聚责任分配原则,因此一般只在很简单的情况下应用。

二、工厂方法模式(FACTORY METHOD)(重要)(创建型)

工厂方法模式概述

工厂方法模式(FACTORY METHOD)同样属于一种常用的对象创建型设计模式,又称为多态工厂模式,此模式的核心精神是封装类中不变的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。它的核心结构有四个角色,分别是抽象工厂,具体工厂,抽象产品,具体产品。

  • 工厂方法(FactoryMethod)模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。

  • 工厂方法模式是简单工厂模式的衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。

  • 工厂方法模式对简单工厂模式进行了抽象。有一个抽象的Factory类(可以是抽象类和接口),这个类将不再负责具体的产品生产,而是只制定一些规范,具体的生产工作由其子类去完成。在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。

  • 工厂方法模式(Factory Method pattern)是最典型的模板方法模式(Template Method pattern)应用。

简单工厂模式中,我们有两个具体产品,Apple与Banana,如果我们要增加新的具体工厂时。我们就需要修改已经写好的工厂。像这样

public class Pear implements Fruit{  //具体产品
    @Override
    public void get() {
        System.out.println("采集梨子");
    }
}
public class FruitFactory { //工厂
    public static Fruit getFruit(String type) throws InstantiationException, IllegalAccessException{
        //不区分大小写
        if(type.equalsIgnoreCase("Apple")){
            return Apple.class.newInstance();
        }else if(type.equalsIgnoreCase("Banana")){
            return Banana.class.newInstance();
        }else if(type.equalsIgnoreCase("Pear")){
            return Pear.class.newInstance();
        }else{
            System.out.println("找不到相应的实体类");
            return null;
        }
    }
}
public class MainClass {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //实例化一个Apple,用到了工厂类
        Fruit apple = FruitFactory.getFruit("apple");
        //实例化一个Banana,用到了工厂类
        Fruit banana= FruitFactory.getFruit("banana");
        //实例化一个Pear,用到了工厂类
        Fruit pear= FruitFactory.getFruit("pear");
        apple.get();
        banana.get();
        pear.get();
    }
}

可以看到,这样子,只要增加具体产品时,我们就要修改具体工厂,这样子并不符合开放-封闭原则。

所以,根据工厂方法模式我们创建一个抽象工厂

 public interface FruitFactory {
     public Fruit getFruit();
 }

然后再创建相应的具体工厂实现抽象工厂

public class AppleFactory implements FruitFactory {
    @Override
    public Fruit getFruit() {
        return new Apple();
    }
}
public class BananaFactory implements FruitFactory {
    @Override
    public Fruit getFruit() {
        return new Banana();
    }
}

主方法

public class MainClass {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        //获得AppleFactory
        FruitFactory af = new AppleFactory();
        //通过AppleFactory来获得Apple实例对象
        Fruit apple = af.getFruit();
        apple.get();
        
        //获得BananaFactory
        FruitFactory bf = new BananaFactory();
        //通过BananaFactory来获得Apple实例对象
        Fruit banana = bf.getFruit();
        banana.get();
    }
}

可以看到,工厂方法模式,如果要新增具体产品,根本不必动原有工厂代码,只要新建一个新增产品的专属工厂,并实现抽象工厂即可。

工厂方法模式中包含的角色及其职责

  • 抽象工厂(Creator)角色:(FruitFactory)
      是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
  • 具体工厂(Concrete Creator)角色:(AppleFactory、BananaFactory)
      这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建产品对象。
  • 抽象产品(Product)角色:(Fruit)
      工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
  • 具体产品(Concrete Product)角色:(Apple、Banana)
      这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应。

工厂方法模式与简单工厂模式比较

  1. 工厂方法模式与简单工厂模式在结构上的不同不是很明显。工厂方法类的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。
  2. 工厂方法模式之所以有一个别名叫多态性工厂模式是因为具体工厂类都有共同的接口,或者有共同的抽象父类。
  3. 当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好的符合了开放-封闭原则。而简单工厂模式在添加新产品对象后,不得不修改工厂方法,扩展性不好。
  4. 工厂方法模式退化后可以演变成简单工厂模式。

应用场景

工厂方法经常用在以下两种情况中:
    第一种情况是对于某个产品,调用者清楚地知道应该使用哪个具体工厂服务,实例化该具体工厂,生产出具体的产品来。Java Collection中的iterator() 方法即属于这种情况。
    第二种情况,只是需要一种产品,而不想知道也不需要知道究竟是哪个工厂为生产的,即最终选用哪个具体工厂的决定权在生产者一方,它们根据当前系统的情况来实例化一个具体的工厂返回给使用者,而这个决策过程这对于使用者来说是透明的。

三、抽象工厂模式(创建型)

抽象工厂模式概述

抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。

产品族

是指位于不同产品等级结构中,功能相关联的产品组成的家族。一般是位于不同的等级结构中的相同位置上。显然,每一个产品族中含有产品的数目,与产品等级结构的数目是相等的,形成一个二维的坐标系,水平坐标是产品等级结构,纵坐标是产品族。叫做相图。
  当有多个不同的等级结构的产品时,如果使用工厂方法模式就势必要使用多个独立的工厂等级结构来对付这些产品的等级结构。如果这些产品等级结构是平行的,会导致多个平行的工厂等级结构。
  抽象工厂模式使用同一个 工厂等级结构负责这些不同产品等级结构产品对象的创建。
  对于每一个产品族,都有一个具体工厂。而每一个具体工厂创建属于同一个产品族,但是分属于不同等级结构的产品。
  通过引进抽象工厂模式,可以处理具有相同(或者相似)等级结构的多个产品族中的产品对象的创建问题。
  由于每个具体工厂角色都需要负责两个不同等级结构的产品对象的创建,因此每个工厂角色都需要提供两个工厂方法,分别用于创建两个等级结构的产品。既然每个具体工厂角色都需要实现这两个工厂方法,所以具有一般性,不妨抽象出来,移动到抽象工厂角色中加以声明。
  就好比,水果分为
  北方水果:北方苹果,北方香蕉;
  南方水果:南方苹果,南方香蕉;
  热带水果:热带苹果,热带香蕉;
  这样看,北方水果,南方水果,热带水果这就是三个不同的产品族。

抽象工厂模式的例子

首先确定我们的产品族,产品族为南方水果与北方水果,而水果(产品等级)有苹果和香蕉产品等级,所以具体产品为南方苹果,北方苹果,南方香蕉,北方香蕉。
  具体代码如下:首先每一个族中都有苹果和香蕉,所以定义两个抽象类,其中包含一个抽象方法
苹果

 public abstract class Apple implements Fruit{
     public abstract void get();
 }

香蕉

 public abstract class Banana implements Fruit{
     public abstract void get();
 }

在写苹果香蕉的具体产品,并各自继承对应的抽象类。

北方苹果

public class NorthApple extends Apple {
    @Override
    public void get() {
        System.out.println("采集北方苹果");
    }

}

南方苹果

public class SouthApple extends Apple{
    @Override
    public void get() {
        System.out.println("采集南方苹果");
    }
}

北方香蕉

public class NorthBanana extends Banana {
    @Override
    public void get() {
        System.out.println("采集北方香蕉");
    }
}

南方香蕉

public class SouthBanana extends Banana {
    @Override
    public void get() {
        System.out.println("采集南方香蕉");
    }
}

接下来创建工厂,而每一个产品族都对应一个具体的工厂,每个产品族都包含苹果和香蕉,所以每个工厂中都包含苹果和香蕉

抽象工厂

public interface FruitFactory {
    //实例化一个苹果
    public Fruit getApple();
    //实例化一个香蕉
    public Fruit getBanana();
}

北方工厂

public class NorthFactory implements FruitFactory{
    @Override
    public Fruit getApple() {
        return new NorthApple();
    }

    @Override
    public Fruit getBanana() {
        return new NorthBanana();
    }
}

南方工厂

public class SouthFactory implements FruitFactory{
    @Override
    public Fruit getApple() {
        return new SouthApple();
    }

    @Override
    public Fruit getBanana() {
        return new SouthBanana();
    }
}

最后,写一个运行的主方法

public class MainClass {
    public static void main(String[] args) {
        FruitFactory nf = new NorthFactory();
        
        Fruit nApple = nf.getApple();
        nApple.get();
        
        Fruit nBanana = nf.getBanana();
        nBanana.get();
        
        FruitFactory sf = new SouthFactory();
        
        Fruit sApple = sf.getApple();
        sApple.get();
        
        Fruit sBanana = sf.getBanana();
        sBanana.get();
    }
}

最终运行结果

  • 采集北方苹果
  • 采集北方香蕉
  • 采集南方苹果
  • 采集南方香蕉

这时如果想新增一个产品族热带水果,只需新建一个热带产品族的工厂即可,已经建好的南方与北方工厂无需改动,也符合开放-封闭原则。

但缺点也很明显,从产品等级来看,如果想新增一个产品等级,例如上面的例子只有苹果与香蕉,如果现在新增一个葡萄,就需要在抽象工厂中添加一个葡萄抽象方法,再在每一个具体工厂中实现此方法。这样就完全不符合开放-封闭原则了。

优点:

  • 它分离了具体的类
  • 它使得易于交换产品系列
  • 它有利于产品的一致性

缺点:

  • 难以支持新种类的产品

抽象工厂模式中包含的角色及其职责

  • 抽象工厂(Creator)角色:(FruitFactory)

是抽象工厂模式的核心,包含对多个产品结构的声明,任何工厂类都必须实现这个接口。

  • 具体工厂(Concrete Creator)角色:(AppleFactory、BananaFactory)

这是实现抽象工厂接口的具体工厂类,负责实例化某个产品族中的产品对象。

  • 抽象产品(Product)角色:(Fruit)

抽象工厂模式所创建的对象的父类,它负责描述所有实例所共有的公共接口。

  • 具体产品(Concrete Product)角色:(Apple、Banana)

抽象模式所创建的具体实例对象。

抽象工厂中方法对应产品结构,具体工厂对应产品族。

四、单例模式(创建型)

单例模式概述

单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的一个实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。

其实,GoF对单例模式的定义是:保证一个类,只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。

为什么要用单例模式

这是因为在应用系统开发时,我们常常有以下需求:

  • 在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象。
  • 在整个程序空间使用全局变量,共享资源。
  • 在大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

因为单例模式可以保证为一个类只生成唯一的实例对象,所以这些情况,单例模式就派上用场了。

单例模式分类

1、饿汉式(在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快,同时无法做到延时加载)

public class Person {
    public static final Person person = new Person();

    //构造函数私有化
    private Person(){}

    //提供一个全局的静态方法
    public static Person getPerson(){
        return person;
    }
}

2、懒汉式(在类加载时不初始化,可以延时加载)

懒汉式可以分为两种,一种线程安全,一种线程不安全

2.1懒汉式(线程不安全,但效率高)

public class Person {
    public static Person person = null;

    //构造函数私有化
    private Person(){}

    //提供一个全局的静态方法
    public static Person getPerson(){
        if(person == null){
            person = new Person();
        }
        return person;
    }
}

2.2懒汉式(线程安全,但效率低)

public class Person {
    public static Person person = null;

    //构造函数私有化
    private Person(){}

    //提供一个全局的静态方法
    public static synchronized Person getPerson(){
        if(person == null){
            person = new Person();
        }
        return person;
    }
}

3、静态内部类(可以延时加载)

这种方法是饿汉式的一种升级,这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,同时实现了延时加载。

public class Person {
    private static class PersonHolder{
        private static final Person person = new Person();
    }

    //构造函数私有化
    private Person(){}

    //提供一个全局的静态方法  
    public static final Person getPerson(){
        return PersonHolder.person;
    }
}

4、双重检查(对懒汉式的升级,效率更高)

public class Person {
    public static Person person = null;

    //构造函数私有化
    private Person(){}

    //提供一个全局的静态方法
    public static Person getPerson(){
        if(person == null){
            synchronized(Person.class){
                if(person == null){
                    person = new Person();
                }
            }
        }
        return person;
    }
}

这样写,只把新建实例的代码放到同步锁中,为了保证线程安全再在同步锁中加一个判断,虽然看起来更繁琐,但是同步中的内容只会执行一次,执行过后,以后经过外层的if判断后,都不会在执行了,所以不会再有阻塞。程序运行的效率也会更加的高。

五、原型模式(创建型)

原型模式概述

原型(Prototype)模式是一种对象创建型模式,他采取复制原型对象的方法来创建对象的实例。使用原型模式创建的实例,具有与原型一样的数据。

原型模式的特点

  • 由原型对象自身创建目标对象。也就是说,对象创建这一动作发自原型对象本身。
  • 目标对象是原型对象的一个克隆。也就是说,通过原型模式创建的对象,不仅仅与原型对象具有相同的结构,还与原型对象具有相同的值。
  • 根据对象克隆深度层次的不同,有浅度克隆与深度克隆。

先写一个支持克隆的类

//如果要克隆就必须实现Cloneable接口
public class Person implements Cloneable{
    //可能会抛出不支持克隆异常,原因是没有实现Cloneable接口
    @Override
    protected Person clone(){
        try{
            return (Person) super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            return null;
        }
    }
}

这个样子,就说明这个类可以克隆了。

这样克隆

public class MainClass {
    public static void main(String[] args) {
        Person person1 = new Person();
        
        Person person2 = person1.clone();
    }
}

这样子克隆并不等同于Person p2 = p1;像Person p2 = p1;指的是在栈中创建一个变量p2,将p1的内存地址赋给p2,其实指的是同一个对象。而克隆是复制出一份一模一样的对象,两个对象内存地址不同,但对象中的结构与属性值一模一样。

这种不通过 new 关键字来产生一个对象,而是通过对象拷贝来实现的模式就叫做原型模式,这个模式的核心是一个clone( )方法,通过这个方法进行对象的拷贝,Java 提供了一个 Cloneable 接口来标示这个对象是可拷贝的,为什么说是“标示”呢?翻开 JDK 的帮助看看 Cloneable 是一个方法都没有的。

这个接口只是一个标记作用,在 JVM 中具有这个标记的对象才有可能被拷贝,所以覆盖了覆盖clone()方法就可以了。

在 clone()方法上增加了一个注解@Override, 没有继承一个类为什么可以重写呢?在 Java 中所有类的父类是Object 类,每个类默认都是继承了这个类,所以这个用上@Override是非常正确的。原型模式虽然很简单,但是在 Java 中使用原型模式也就是 clone 方法还是有一些注意事项的:

对象拷贝时,类的构造函数是不会被执行的。 一个实现了 Cloneable 并重写了 clone 方法的类 A,有一个无参构造或有参构造 B,通过 new 关键字产生了一个对象 S,再然后通过 S.clone()方式产生了一个新的对象 T,那么在对象拷贝时构造函数 B 是不会被执行的, 对象拷贝时确实构造函数没有被执行,这个从原理来讲也是可以讲得通的,Object 类的 clone 方法的 原理是从内存中(具体的说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数 没有被执行也是非常正常的了。

还有就是深度克隆与浅度克隆

  • 浅克隆是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
  • 深克隆不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。

举例来说更加清楚:在这里插入图片描述

浅度克隆

//如果要克隆就必须实现Cloneable接口
public class Person implements Cloneable{
    private String name;
    private String sex;
    private List<String> list;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    //可能会抛出不支持克隆异常,原因是没有实现Cloneable接口
    @Override
    protected Person clone(){
        try{
            return (Person) super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            return null;
        }
    }
}

这就是浅度克隆,当被克隆的类中有引用对象(String或Integer等包装类型除外)时,克隆出来的类中的引用变量存储的还是之前的内存地址,也就是说克隆与被克隆的对象(切记是其中的引用对象)是同一个。这样的话两个对象共享了一个私有变量,所有人都可以改,是一个种非常不安全的方式,在实际项目中使用还是比较少的。

深度拷贝

//如果要克隆就必须实现Cloneable接口
public class Person implements Cloneable{
    private String name;
    private String sex;
    private List<String> list;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public List<String> getList() {
        return list;
    }
    public void setList(List<String> list) {
        this.list = list;
    }
    //可能会抛出不支持克隆异常,原因是没有实现Cloneable接口
    @Override
    protected Person clone(){
        try{
            Person person = (Person) super.clone();
            List<String> newList = new ArrayList();
            
            for(String str : this.list){
                newList.add(str);
            }
            person.setList(newList);
            return person;
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
            return null;
        }
    }
}

这样就完成了深度拷贝,两种对象互为独立,属于单独对象。

注意:final 类型修饰的成员变量不能进行深度拷贝。

原型模式的使用场景

  • 在创建对象的时候,我们不只是希望被创建的对象继承其基类的基本结构,还希望继承原型对象的数据。
  • 希望对目标对象的修改不影响既有的原型对象(深度克隆的时候可以完全互不影响)。
  • 隐藏克隆操作的细节,很多时候,对对象本身的克隆需要涉及到类本身的数据细节。
  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等;
  • 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息,建立了一个完整的个性对象。

六、建造者模式(创建型)

建造者模式概述

Builder模式也叫建造者模式或者生成器模式,是由GoF提出的23种设计模式中的一种。Builder模式是一种对象创建型模式之一,用来隐藏复合对象的创建过程,它把复合对象的创建过程加以抽象,通过子类继承和重载的方式,动态地创建具有复合属性的对象。

建造者模式的结构
在这里插入图片描述

角色

在这样的设计模式中,有以下几个角色:

  1. builder:为创建一个产品对象的各个部件指定抽象接口。
  2. ConcreteBuilder:实现Builder的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
  3. Director:构造一个使用Builder接口的对象。
  4. Product:表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

首先,举个例子,建造者模式我们比方我们要造个房子。

房子的图纸

public class House {
    //地板
    private String floor;
    //墙
    private String wall;
    //房顶
    private String roof;
    
    public String getFloor() {
        return floor;
    }
    public void setFloor(String floor) {
        this.floor = floor;
    }
    public String getWall() {
        return wall;
    }
    public void setWall(String wall) {
        this.wall = wall;
    }
    public String getRoof() {
        return roof;
    }
    public void setRoof(String roof) {
        this.roof = roof;
    }
}

有了图纸后,最笨的方法就是自己造房子

客户端

public class MainClass {
    public static void main(String[] args) {
        //客户直接造房子
        House house = new House();
        
        house.setFloor("地板");
        house.setWall("墙");
        house.setRoof("屋顶");
        
        System.out.println(house.getFloor());
        System.out.println(house.getWall());
        System.out.println(house.getRoof());
    }
}

可是这样的方法不是很好,真正我们造房子都是找施工队,所以我们要把造房子分离出来,交给施工队

新建一个施工队,为了扩展性,声明一个施工队的接口。

public interface HouseBuilder {
    //修地板
    public void makeFloor();
    //修墙
    public void makeWall();
    //修屋顶
    public void makeRoof();
    //获得修好的房子
    public House getHouse();
}

新建一个施工队,实现此接口

public class LoufangBuilder implements HouseBuilder{
    House house = new House();
    
    @Override
    public void makeFloor() {
        house.setFloor("楼房->地板");
    }

    @Override
    public void makeWall() {
        house.setWall("楼房->墙");
    }

    @Override
    public void makeRoof() {
        house.setRoof("楼房->屋顶");
    }

    @Override
    public House getHouse() {
        return house;
    }
}

客户端

public class MainClass {
    public static void main(String[] args) {
        //施工队造房子
        HouseBuilder loufangBuilder = new LoufangBuilder();
        loufangBuilder.makeFloor();
        loufangBuilder.makeWall();
        loufangBuilder.makeRoof();
        
        House house = loufangBuilder.getHouse();
        System.out.println(house.getFloor());
        System.out.println(house.getWall());
        System.out.println(house.getRoof());
    }
}

可以看到,这样子造房子就交给施工队了,但可以看到造房子的具体细节还在客户端里,如图。
在这里插入图片描述
这就相当于我们在指导施工队干活,这肯定不是最好的方案,最好的解决方案,是由一个设计师也可以说是指挥者来指导工程队,所以在新建一个指挥者。

public class HouseDirector {
    private HouseBuilder houseBuilder;
    
    public HouseDirector(HouseBuilder houseBuilder){
        this.houseBuilder = houseBuilder;
    }
    
    public void make(){
        houseBuilder.makeFloor();
        houseBuilder.makeWall();
        houseBuilder.makeRoof();
    }
}

客户端

public class MainClass {
    public static void main(String[] args) {
        //施工队造房子
        HouseBuilder loufangBuilder = new LoufangBuilder();
//        loufangBuilder.makeFloor();
//        loufangBuilder.makeWall();
//        loufangBuilder.makeRoof();
        HouseDirector houseDirector = new HouseDirector(loufangBuilder);
        houseDirector.make();
        
        House house = loufangBuilder.getHouse();
        System.out.println(house.getFloor());
        System.out.println(house.getWall());
        System.out.println(house.getRoof());
    }
}

这样子,把施工队交给这个设计者,施工细节的工作就由这个设计者执行了。

当然,还有一种写法,有一些细微的改动,也是更常用的,就是设计者(Director)不在构造时传入builder,而是在调用方法时,才传入,像这样

public class HouseDirector {
    public void make(HouseBuilder houseBuilder){
        houseBuilder.makeFloor();
        houseBuilder.makeWall();
        houseBuilder.makeRoof();
    }
}

客户端

public class MainClass {
    public static void main(String[] args) {
        //施工队造房子
        HouseBuilder loufangBuilder = new LoufangBuilder();

        HouseDirector houseDirector = new HouseDirector();
        houseDirector.make(loufangBuilder);
        
        House house = loufangBuilder.getHouse();
        System.out.println(house.getFloor());
        System.out.println(house.getWall());
        System.out.println(house.getRoof());
    }
}

这样子,出来的效果是一样的。

这就是一个简单的建造者模式

这样也提高了系统的扩展性与可维护性,如果不想造楼房了,想造一个别墅,只需新增一个别墅施工队就好了,像这样

public class BieshuBuilder implements HouseBuilder{
    House house = new House();
    
    @Override
    public void makeFloor() {
        house.setFloor("别墅->地板");
    }

    @Override
    public void makeWall() {
        house.setWall("别墅->墙");
    }

    @Override
    public void makeRoof() {
        house.setRoof("别墅->屋顶");
    }

    @Override
    public House getHouse() {
        return house;
    }
}

客户端只需把施工队换成别墅施工队

public class MainClass {
    public static void main(String[] args) {
        //施工队造房子
        HouseBuilder bieshuBuilder = new BieshuBuilder();//只需要修改这里

        HouseDirector houseDirector = new HouseDirector();
        houseDirector.make(bieshuBuilder);
        
        House house = bieshuBuilder.getHouse();
        System.out.println(house.getFloor());
        System.out.println(house.getWall());
        System.out.println(house.getRoof());
    }
}

适用范围

  • 对象的创建:Builder模式是为对象的创建而设计的模式
  • 创建的是一个复合对象:被创建的对象为一个具有复合属性的复合对象
  • 关注对象创建的各部分的创建过程:不同的工厂(这里指builder生成器)对产品属性有不同的创建方法

八、装饰者模式(创建型)

装饰者模式概述

装饰( Decorator )模式又叫做包装模式。通过一种对客户端透明的方式来扩展对象的功能,是继承关系的一个替换方案。他是23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

为什么要使用装饰模式

  • 多用组合,少用继承。

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。

  • 类应设计的对扩展开放,对修改关闭。

之前我们假如实现这样一个功能,建造出各种各样不同功能的车,咱们是这样实现的:

首先,新建一个Car接口,定义了所有车的基本功能,就是跑run(),和展示自己功能的方法show()。

public interface Car {
     public void show();
     public void run();
 }

然后,在创建各个具体的车实现Car接口

会跑的车

public class RunCar  implements Car{

    @Override
    public void show() {
        this.run();
        
    }

    @Override
    public void run() {
        System.out.println("可以跑");
        
    }
}

会游泳的车

public class SwimCar implements Car {

    @Override
    public void show() {
        this.run();
        this.swim();
    }

    @Override
    public void run() {
        System.out.println("可以跑");

    }

    public void swim(){
        System.out.println("可以游泳");
    }
}

会飞的车

public class FlyCar implements Car {

    @Override
    public void show() {
        this.run();
        this.fly();
    }

    @Override
    public void run() {
        System.out.println("可以跑");

    }

    public void fly(){
        System.out.println("可以飞");
    }
}

这时,写一个客户端展示每个车的功能

public class MainClass {
    public static void main(String[] args) {
//        Car car = new RunCar();
//        Car car = new FlyCar();
        Car car = new SwimCar();
        car.show();
    }
}

这样,每新增一种车,就要新写一个子类实现或继承其他类或接口,就相当于,每新增一种功能,就要新建一辆车。

这时,我们还有一种替代方案,就是使用装饰模式

首先,新建一个Car接口,和一个基础的Car的实现类RunCar,因为只要是车一定有跑的功能,这两个和上面一样,不在重复写了。

然后在新建装饰类,不同的功能建不同的装饰类

1、新建一个装饰类父类,实现Car接口,提供一个有参的构造方法,共有的方法show(),私有的Car成员变量,并为之提供get(),set()方法。

一定要继承Car,因为装饰过后,还是一辆车

public abstract class CarDecorator implements Car{
    private Car car;
    
    public CarDecorator(Car car){
        this.car = car;
    }
    
    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    public abstract void show();
}

2、为不同的装饰新建装饰类,并继承CarDecorator抽象类

(1)游泳装饰类,覆盖抽象方法,在新增特有的方法

public class SwimCarDecorator extends CarDecorator {

    public SwimCarDecorator(Car car){
        super(car);
    }
    
    @Override
    public void show() {
        this.getCar().show();
        this.swim();
    }
    
    public void swim(){
        System.out.println("可以游泳");
    }

    @Override
    public void run() {
    }
}

(2)飞行装饰类

public class FlyCarDecorator extends CarDecorator {

    public FlyCarDecorator(Car car){
        super(car);
    }
    @Override
    public void show() {
        this.getCar().show();
        this.fly();
    }
    
    public void fly(){
        System.out.println("可以飞");
    }
    
    @Override
    public void run() {
    }
}

添加客户端,执行

public class MainClass {
    public static void main(String[] args) {
        Car car = new RunCar();
        Car swimCar = new SwimCarDecorator(car);
        swimCar.show();
    }
}

这样,就不等于是,每新增一个功能就新建一辆车了,而是基础有一个RunCar,这是最基本的车,装饰类就相当于在基本的车的基础上,添加功能,装饰这台最基本的车。

所以一定要继承Car,因为装饰过后,还是一辆车,我们可以直接Car swimCar = new SwimCarDecorator(car);用Car来创建变量。

同样,继承关系如果每一个功能都要添加一个新的子类,如果,一辆车已经拥有了游泳和飞行的功能,这时有新增同时拥有游泳和飞行的Car,继承关系就需要在新建一个子类同时拥有这两个功能,而装饰模式什么都不需要新增,对基础的RunCar修饰两遍即可。像这样:

public class MainClass {
    public static void main(String[] args) {
        Car car = new RunCar();
        Car swimCar = new SwimCarDecorator(car);
        Car flySwimCar = new FlyCarDecorator(swimCar);
        flySwimCar.show();
    }
}

这样子,最后的flySwimCar就同时拥有了飞行和游泳的功能,这也是装饰类继承Car的原因,这样子装饰类才能当做参数放进构造方法中。

装饰模式的结构图

装饰模式的结构图

装饰模式的角色与职责

  1. 抽象组件角色(Component): 一个抽象接口,是被装饰类和装饰类的父接口。(Car)
  2. 具体组件角色(ConcreteComponent):为抽象组件的实现类。(RunCar)
  3. 抽象装饰角色(Decorator):包含一个组件的引用,并定义了与抽象组件一致的接口。(CarDecorator)
  4. 具体装饰角色(ConcreteDecorator):为抽象装饰角色的实现类。负责具体的装饰。(FlyCarDecorator、SwimCarDecorator)

Decorator模式使用范围

  1. 需要扩展一个类的功能,或给一个类添加附加职责。
  2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
  4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

装饰模式的优缺点

优点

  • Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点

  • 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
  • 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

九、策略模式(行为型)

策略模式概述

Strategy模式也叫策略模式是行为模式之一,它对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略)。Strategy模式主要用来平滑地处理算法的切换 。

举个例子:假如有两个加密算法,我们分别调用他们,之前我们可以这么写

先写一个算法接口

public interface Strategy {
     //加密
     public void encrypt();
 }

再写两个对应的实现类

public class MD5Strategy implements Strategy {

    @Override
    public void encrypt() {
        System.out.println("执行MD5加密");
    }
}
public class RSAStrategy implements Strategy{

    @Override
    public void encrypt() {
        System.out.println("执行RSA加密");
    }
}

最后,写一个主函数调用

public class MainClass {
    public static void main(String[] args) {
        Strategy md5 = new MD5Strategy();
        md5.encrypt();
        
        Strategy rsa = new RSAStrategy();
        rsa.encrypt();
    }
}

这样使我们使用传统继承关系方式来实现此功能,接下来使用策略模式

首先、新建一个Context类,这个类是策略模式的核心类,类似于一个工厂,它包含了所有算法类的所有方法。

public class Context {
    private Strategy strategy;
    
    public Context(Strategy stratrgy){
        this.strategy = stratrgy;
    }
    
    public void encrypt(){
        this.strategy.encrypt();
    }
}

两个算法类都不用改变,主函数调用改为

public class MainClass {
    public static void main(String[] args) {
        Context md5 = new Context(new MD5Strategy());
        md5.encrypt();
        
        Context rsa = new Context(new RSAStrategy());
        rsa.encrypt();
    }
}

这样子,用户就只需要关心context即可,也不必关心算法的具体实现,这就是一个简单了策略模式例子

策略模式的结构

在这里插入图片描述

策略模式的角色与职责

  1. Strategy: 策略(算法)抽象。(Strategy接口)
  2. ConcreteStrategy:各种策略(算法)的具体实现。(MD5Strategy、RSAStrategy)
  3. Context:策略的外部封装类,或者说策略的容器类。根据不同策略执行不同的行为。策略由外部环境决定。(Context)

策略模式的优缺点

优点:

  • 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码。

  • 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。

  • 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

  • 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

应用场景

  1. 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
  2. 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
  3. 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。

九、观察者模式(行为型)(重要)

观察者模式概述

Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。

Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。

观察者模式的结构

在这里插入图片描述

观察者模式的角色和职责

  1. Subject(被观察者)

    被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。

  2. ConcreteSubject

    被观察者的具体实现。包含一些基本的属性状态及其他操作。

  3. Observer(观察者)

    接口或抽象类。当Subject的状态发生变化时,Observer对象将通过一个callback函数得到通知。

  4. ConcreteObserver

    观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。

而被观察者想要起作用,就必须继承java.util包下的Observable类,这是它的方法,后面会有介绍。

构造方法摘要

Observable() 
构造一个带有零个观察者的 Observable。

方法摘要

 void	addObserver(Observer o) 
 		  //如果观察者与集合中已有的观察者不同,则向对象的观察者集中添加此观察者。
protected  void	clearChanged() 
          //指示对象不再改变,或者它已对其所有的观察者通知了最近的改变,所以 hasChanged 方法将返回 false。
 int	countObservers() 
          //返回 Observable 对象的观察者数目。
 void	deleteObserver(Observer o) 
          //从对象的观察者集合中删除某个观察者。
 void	deleteObservers() 
          //清除观察者列表,使此对象不再有任何观察者。
 boolean	hasChanged() 
          //测试对象是否改变。
 void	notifyObservers() 
          //如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。
 void	notifyObservers(Object arg) 
          //如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。
protected  void	setChanged() 
          //标记此 Observable 对象为已改变的对象;现在 hasChanged 方法将返回 true。

下面写一个例子:新建一个Person类

public class Person {
    private String name;
    private String sex;
    private int age;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

我们要做的就是监听成员变量name,sex,age的变化,在数值变化是,执行我们的操作,所以Person就是被观察者,所以server必须继承Observable,而Observable中有这三个方法:

  1. notifyObservers() : 如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并调用 clearChanged 方法来指示此对象不再改变。

这个方法是通知观察者被观察者是否改变的,只要hasChanged()方法指示的对象改变,就会调用观察者中的方法。

  1. hasChanged() : 测试对象是否改变。
  2. setChanged() :标记此 Observable 对象为已改变的对象;现在 hasChanged 方法将返回 true。

所以,如果想观察成员变量是否改变,就要在set方法中,执行setChanged()与notifyObservers()。

所以,被观察者应该改为:

import java.util.Observable;

public class Person extends Observable{
    private String name;
    private String sex;
    private int age;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        this.setChanged();
        this.notifyObservers();
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
        this.setChanged();
        this.notifyObservers();
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        this.setChanged();
        this.notifyObservers();
    }
}

有了被观察者,就要有观察者,观察者必须实现java.util包下的Observer接口,并重写update(Observable o, Object arg)方法,当被观察者改变时,就会执行update()方法

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

public class MyObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("对象已改变");
    }

}

现在,就可以执行看一看了。不过在执行set()方法之前一定要使用addObserver(Observer o) 这个方法注册观察者,不然不会生效。

public class MainClass {
    public static void main(String[] args) {
        Person person = new Person();
        //注册观察者
        person.addObserver(new MyObserver());
        person.setName("小明");
        person.setSex("男");
        person.setAge(18);
    }
}

输出结果是这样的:

      对象已改变
      对象已改变
      对象已改变

同时,notifyObservers()为什么是s结尾呢,因为我们可以同时注册多个观察者,这样写

public class MainClass {
    public static void main(String[] args) {
        Person person = new Person();
        //注册观察者
        person.addObserver(new MyObserver());
        person.addObserver(new MyObserver());
        
        person.setName("小明");
        person.setSex("男");
        person.setAge(18);
    }
}

我们注册两个观察者,两个都会生效,结果就变为了:

     对象已改变
     对象已改变
     对象已改变
     对象已改变
     对象已改变
     对象已改变

还有三个方法deleteObserver(Observer o) ,deleteObservers() ,countObservers()

public class MainClass {
    public static void main(String[] args) {
        Person person = new Person();
        //注册观察者
        MyObserver myObserver = new MyObserver();
        person.addObserver(myObserver);
        person.addObserver(new MyObserver());
        //获得当前对象已注册的观察者数目
        person.countObservers();
        //删除指定的一个观察者
        person.deleteObserver(myObserver);
        //删除该对象全部观察者
        person.deleteObservers();
        
        person.setName("小明");
        person.setSex("男");
        person.setAge(18);
    }
}

观察者模式的典型应用

  1. 侦听事件驱动程序设计中的外部事件
  2. 侦听/监视某个对象的状态变化
  3. 发布者/订阅者(publisher/subscriber)模型中,当一个外部事件(新的产品,消息的出现等等)被触发时,通知邮件列表中的

十、享元模式(结构型)

享元模式概述

lyweight模式也叫享元模式,是构造型模式之一,它通过与其他类似对象共享数据来减小内存占用。它使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。 
在这里插入图片描述

享元模式的角色和职责

  1. 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。
  2. 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
  3. 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键。
  4. 客户端角色 :维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。

两个状态

  1. 内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的
  2. 外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。

举个例子

首先,创建一个抽象享元角色

public interface Flyweight {
     public void display();
 }

接着,创建具体享元角色

public class MyFlyweight implements Flyweight{
    private String str;
    
    public MyFlyweight(String str){
        this.str = str;
    }
    
    public void display(){
        System.out.println(str);
    }
}

如果,不使用享元模式的话,不创建享元工厂,直接,创建客户端,代码如下:

public class MainClass {
    public static void main(String[] args) {
        Flyweight myFlyweight1 = new MyFlyweight("a");
        Flyweight myFlyweight2 = new MyFlyweight("b");
        Flyweight myFlyweight3 = new MyFlyweight("a");
        Flyweight myFlyweight4 = new MyFlyweight("d");
        
        myFlyweight1.display();
        myFlyweight2.display();
        myFlyweight3.display();
        myFlyweight4.display();
        
        System.out.println(myFlyweight1 == myFlyweight3);
    }
}

这样子,运行结果为:

在这里插入图片描述
  这样子,可以看到,第一个与第三个明明都是a,但却不是同一个对象,说明虽然对象内部一模一样,但却创建了两个对象,这样就浪费了资源。

如果用到享元模式,继续创建享元工厂

public class MyFlyweightFactory {
    private Map<String,MyFlyweight> pool;
    
    public MyFlyweightFactory(){
        pool = new HashMap<String,MyFlyweight>();
    }
    
    public Flyweight getMyFlyweight(String str){
        MyFlyweight myFlyweight = pool.get(str);
        
        //若池中没有则创建一个新的并放入池中,若池中已存在,则返回池中的
        if(myFlyweight == null){
            myFlyweight = new MyFlyweight(str);
            pool.put(str, myFlyweight);
        }
        
        return myFlyweight;
    }
}

这样,修改客户端

public class MainClass {
    public static void main(String[] args) {
        MyFlyweightFactory myFlyweightFactory = new MyFlyweightFactory();
        Flyweight myFlyweight1 = myFlyweightFactory.getMyFlyweight("a");
        Flyweight myFlyweight2 = myFlyweightFactory.getMyFlyweight("b");
        Flyweight myFlyweight3 = myFlyweightFactory.getMyFlyweight("a");
        Flyweight myFlyweight4 = myFlyweightFactory.getMyFlyweight("d");
        
        myFlyweight1.display();
        myFlyweight2.display();
        myFlyweight3.display();
        myFlyweight4.display();
        
        System.out.println(myFlyweight1 == myFlyweight3);
    }
}

此时,结果就变为了

在这里插入图片描述
  可以看到,第一个与第三个变为了同一个对象,一模一样的对象只创建一次,节约了资源,这样,享元模式的作用就达到了。

使用场景

如果一个应用程序使用了大量的对象,而这些对象造成了很大的存储开销的时候就可以考虑是否可以使用享元模式。
例如,如果发现某个对象的生成了大量细粒度的实例,并且这些实例除了几个参数外基本是相同的,如果把那些共享参数移到类外面,在方法调用时将他们传递进来,就可以通过共享大幅度单个实例的数目。

猜你喜欢

转载自blog.csdn.net/qq_21918021/article/details/88655872