[设计模式]二十三种设计模式

可复用是设计模式的目标,面向对象是实现的手法(对于软件设计中普遍存在或反复出现的问题,提出的解决方案)。使用设计模式,使得项目的可维护性和规范性会提高

注:架构和数据库领域也都有设计模式

多态存在的三个前提:
1.要有继承关系
2.子类要重写父类的方法
3.父类引用指向子类对象(Animal animal = new Cat(); 其中Cat是Animal的子类)
Java中多态的弊端:
1.不能使用子类特有的成员属性
2.不能使用子类特有的成员方法
Java中多态的好处:
减少多余对象的创建,不用为了使用子类的某个方法重新在堆内存中开辟一个新的子类对象

解决Java中多态弊端的方法:
将父类引用强制转换成子类引用,即可通过子类引用访问子类特有成员与方法

注:Spring中原型bean的创建,就是原型模式的应用

设计模式的七大原则:

  • 单一职责
  • 接口隔离
  • 依赖倒转(倒置)
  • 里氏替换
  • 开闭(ocp)原则
  • 迪米特法则
  • 合成复用
    单例设计模式有八种:
    饿汉式 2种
    懒汉式 3种
    双重检查(多线程中,解决冲突同步问题,以及保证懒加载)
    静态内部类(实现多线程并发,解决懒加载问题)
    枚举

###############(学习完设计模式后,将之前所做过的项目中可能使用设计模式的地方列举出来)
在实际项目中,使用过哪些设计模式,怎样使用的?解决了什么问题?
ps:面向对象(00)=>功能模块[设计模式+算法(数据结构)]=>框架[使用到多种设计模式]=>架构[服务器集群]

@@@设计模式的七大原则

设计模式的目的:
代码重用性(相同功能的代码,不用多次编写)
可读性(编程规范性,便于其他人的阅读和理解)
可扩展性(便于增加新的功能)
可靠性(新增功能后,对原来的功能没有影响)
使程序呈现高内聚与低耦合的特性
设计模式遵循的核心其实是七大原则,23种设计模式只是针对具体应用场景应如何应用七大原则
一、单一职责原则
对类来说,一个类应只负责一项职责(e.如A类负责两个不同的职责,当职责1需求变更时,可能造成职责2执行错误。所以需要将类A的粒度分解为A1与A2)

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

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

三、依赖倒转原则
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象(抽象类或接口)
- !!细节应该依赖抽象,而不是抽象依赖细节
- 依赖倒转(倒置)的中心思想是面向接口变成
- 设计理念:相对于细节的多变性,抽象的东西要稳定得多。以抽象为基础搭建的架构比以细节为基础的架构要稳定得多。在Java中,抽象值的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的的任务交给他们的实现类去做

依赖关系传递的三种方式:
- 接口传递
    /*公用部分*/
    interface IOpenAndClose {public void open(ITV tv);}
    interface ITV {public void play();}
    class ChangHong inplements ITV {
        @Override
        public void play(){...}}
    /*通过接口传递实现依赖关系传递*/
    class OpenAndClose implements IOpenAndClose{
        public void open(ITV tv){tv.play();}
    }
    main(){
        ChangHong changHong = new ChangHong();
        OpenAndClose openAndClose = new OpenAndClose();
        openAndClose.open(changHong);
    }
- 构造方法传递
    /*通过构造方法实现依赖关系传递*/
    class OpenAndClose implements IOpenAndClose{
        public ITV tv;
        public OpenAndClose(ITV tv){this.tv = tv;}
        public void open(){this.tv.play();}}
    main(){
        ChangHong changHong = new ChangHong();
        OpenAndClose openAndClose = new OpenAndClose(changHong);
        openAndClose.open();
    }
- setter方法传递
    /*通过setter方法实现依赖关系传递*/
    class OpenAndClose implements IOpenAndClose {
        private ITV tv;
        public void setTv(ITV tv){this.tv = tv;}
        public void open(){this.tv.play();}
    }
    main(){
        OpenAndClose openAndClose = new OpenAndClose;
        openAndClose.setTv(new ChangHong());
        openAndClose.open();
    }
依赖倒转注意事项:
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
- 继承时遵循里氏替换原则

四、里氏替换原则(继承需要注意什么,或为防止继承的弊端需遵循的设计规范)
- 继承包含了一层含义:父类中已实现的方法,实际是规范和契约已约定。虽不强制所有子类必须遵循这些契约,但如果子类对这些已实现的方法任意修改,就会对继承体系造成破坏
- 继承的弊端:
给程序带来侵入性
程序的可移植性降低
增加对象间的耦合性(当父类发生变化,所有子类都发生变化)
内容:
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。即所有引用积累的地方必须能透明地使用其子类的对象
- 在使用继承时,在子类中尽量不要重写父类的方法
- 继承其实是让两个类耦合性增强了。如果迫不得已要重写父类方法,尽量通过聚合、组合、依赖等方式来解决
class A {
public int func1(int num1, int num2){return num1-num2;}
}

class B extends A {
// 增加一个新功能,完成两数相加后再加9。并不是有意重写父类func1方法,但影响到了父类原功能
    public int func1(int a, int b){return a+b;}
    public int func2(int a, int b){return func1(a, b)+9;}
}

/*改进后A、B类的创建方法*/
class Base{// 把更加基础的方法和成员写到Base类}
class A extends Base {
    public int func1(int num1, int num2) return num1 - num2;}
}
class B extends Base {
    private A a = new A();
    public int func1(int a, int b){return a+b;}
    public int func2(int a, int b){return func1(a,b)+9;}
    public int func3(int a, int b){return this.a.func1(a, b);}
}
由于没有B与A的继承关系,调用B中方法时,不会自然联想到B中的方法属于A中的方法

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

六、迪米特法则(最少知道原则)
- 一个对象应该对其他对象保持最少的了解
- 类与类之间关系越密切,代码耦合度越大
- 一个类对自己依赖的类知道的越少越好,尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
- 类之间都存在或多或少的耦合。其中,对于出现成员变量、方法参数、方法返回值中的类称为直接的朋友(A类有B类的成员变量、A中存在入参或返参为B类的方法),否则不是直接朋友(A类存在方法调用过程中使用了B类的变量,则A、B互相陌生)。陌生的类最好不要以局部变量的形式出现在类的内部。
ps:如果A中有方法的局部变量为B(通常B是封装在入参的类的一个成员),如果A直接访问了B实体类,相当于A完成了B的逻辑,这是不对的

ps:所有法则都是为了降低类之间的耦合
- 迪米特法则只是要求降低耦合关系,并不是要求完全没有依赖关系

七、合成复用原则
- 尽量使用合成/聚合的方式,而不要使用继承
如果我们只是希望B类使用A类的方法,肯定不能直接使用继承方式
设计原则的核心思想:
找出应用中可能需要变化之处,将其独立出,不要和不需要变化的代码混在一起
针对接口编程,而不针对实现编程
为了交互对象之间的松耦合设计

@@@六大关系(依赖、泛化、实现、关联、聚合、组合)

一、依赖关系(虚线箭头)
在类中用到了对方
是类的成员属性
是方法的返回类型
是方法接收的参数类型
方法中使用到
二、泛化关系(实际是继承关系,是依赖关系的一种特例,实线三角箭头) extends
三、实现关系(依赖关系的一种特例,虚线三角箭头)implements
四、关联关系(依赖关系的一种特例association,实线段)
有导航性,有双向关系或单向关系
- 单向一对一关系
public class Person{private IDCard card;}
public class IDCard{}
- 双向一对一关系
public class Person{private IDCard card;}
public class IDCard{private Person person;}
具有多重性
五、聚合关系(是关联关系的一种特例,实线空菱形)
表示整体与部分之间的关系
整体与负分可以分开
也具有导航性与多重性(单聚合/多聚合)
六、组合关系(聚合关系的一种特例,实线实菱形)
表示整体概念的类与其组成部分概念的类共生(创建整体类时,组成部分类也创建;整体类销毁时组成部分也销毁)

@@@设计模式概述
1.创建型模式:单例(控制类只能有一个)、抽象工厂、原型(实现深拷贝浅拷贝)、建造者、工厂
2.结构型模式(站在软件结构考虑,让软件结构的伸缩性弹性更好):适配器、桥接、装饰(解决类爆炸问题)、组合、外观、享元、代理
3.行为型模式(方法的调用更合适):模板方法、命令、访问者、迭代器、观察者、中介者、备忘录、解释器(Interperter)、状态、策略、职责链(责任链)

########创建型模式(对象实例化的模式,用于解耦对象的实例化过程)
一、单例模式
采取一定的方法,在模式层面保证整个系统中,某个类只能存在一个类的实例
ps:单例模式和享元模式都是为了避免重复创建对象,但其本质是不一样的
- 单例模式只有一个实例,享元模式可有多个实例,通过一个共享容器来存储不同的对象
- 单例强调减少实例化提升性能,常用于需频繁创建与销毁实例化对象或创建与销毁实例化对象时非常消耗资源的类(e.连接池、线程池),享元则强调共享相同对象或对象属性,节约内存使用空间
应用场景:
实现方法:
1.饿汉式(静态常量)
步骤:
构造器私有化(防止new)
类的内部创建对象
向外暴露一个静态的公共方法
代码实现:
class Singleton{
private Singleton(){}
private final static Singleton instance = new Singleton();
public static Singleton getInstance(){return singleton;}
}
优点:写法简单,在类装载的时候完成实例化,避免了线程同步问题
缺点:在类装载时候完成实例化,没有达到懒加载效果。如果从始至终从未使用过这个实例,则会造成内存浪费
2.饿汉式(静态代码块)
代码实现:
class Singleton{
private Singleton(){}
private static Singleton instance;
static{instance = new Singleton();}
public static Singleton getInstance(){return singleton;}
优缺点:同上
3.懒汉式(线程不安全)
代码实现:
class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){instance = new Singleton();}
return instance;
}
}
优点:实现了懒加载
缺点:只能在单线程下使用,多线程下可能会产生多个实例。实际开发中不能使用这种方式
4.懒汉式(线程安全,同步方法)
代码实现:
class Singleton{
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){instance = new Singleton();}
return instance;
}
}
优点:解决了线程不安全问题
缺点:效率太低,每个线程想获得类的实例中,都需要同步。其实创建实例的逻辑只需执行一次,获取实例的逻辑无需同步。实际开发中不推荐使用
5.!!双重检查
代码实现:
class Singleton{
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
synchronized (Singleton.class){
if(instance == null){instance = new Singleton();}
}
return instance;
}
}
ps:volatile关键字保证了变量的可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。synchronized和lock也可以保证可见性,但是封锁资源可能会导致多线程下效率运行降低
优点:解决了线程安全问题,解决了懒加载问题,同时延迟加载保证了效率,建议使用
6.!!静态内部类实现
1.外部类被装在时,静态内部类并不会立即被装在
2.装在静态内部类只会被装载一次
代码实现:
class Singleton{
private static volatile Singleton instance;
private Singleton(){}
private static class Singleton {
// jvm装载静态内部类的底层设计保证了线程安全
private static final Singleton INSTANCE = new Singleton();
}
public static synchronized Singleton getInstance()
{return SingletonInstance.INSTANCE;}
}
优点:可实现懒加载,jvm底层机制保证了线程安全,推荐使用
7.!!枚举
代码实现:
enum Singleton{
INSTANCE;
public void sayOK(){...}
}
调用代码:
main(){
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;// instance1和instance2相同
instance1.sayOK();// 正常通过实例调用实例的方法
}
优点:借助jdk1.5,能避免多线程同步,防止反序列化重新创建新的对象

在jdk源码中的应用:
public class RunTime{
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime(){return currentRuntime;}
}
单例模式的注意事项和细节说明:
1.单例模式保证了系统内存中该类只存在一个对象,节省了系统资源。对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2.当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是new
3.使用场景:需要频繁创建与销毁的对象、创建对象时消耗过多资源(e.重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(数据源、session工厂等)

二、简单工厂模式(静态工厂模式)
应用场景:BeanFactory

  • 传统方式实现点披萨的代码逻辑
    public class OrderPizza{
    public OrderPizza(){
    Pizza pizza = null;
    String orderType;
    do{
    orderType = getType();
    if(orderType.equals("greek")){pizza = new GreekPizza();}
    else if (orderType.equals("cheess")){pizza = new CheessPizza();}
    else(...){...}
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    }while(...);
    }
    // 使用简单工厂模式优化后的构造方法
    SimpleFactory simpleFactory; // 这是聚合了SimpleFactory。如果此处new了一个实例则是组合了SimpleFactory类
    Pizza pizza = null;
    public OrderPizza(SimpleFactory simpleFactory){
    setFactory(simpleFactory);
    }

    private String getType(){
    ...// 用户输入pizza种类
    }

    // 使用简单工厂模式优化的代码部分
    public void setFactory(SimpleFactory simpleFactory){
    String orderType = ...;
    this.simpleFactory = simpleFactory;
    do{
    orderType = getType();
    pizza = this.simpleFactory.createPizza(orderType);
    if(pizza != null){
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    } else{...}
    }while(...);
    }
    }

优点:易于理解,便于操作
缺点:违反了设计模式的ocp原则(对扩展开放,对修改关闭)

  • 使用简单工厂模式优化后的点披萨逻辑
    基本介绍:
    简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例
    定义了一个创建对象的类,由这个类来封装实例化对象的行为
    当需要大量创建某种、某类或某批对象时,就会使用到工厂模式
    public class SimpleFactory{
    public Pizza createPizza(String orderType){
    Pizza pizza = null;
    if(orderType.equals("greek")){pizza = new GreekPizza();}
    else if (orderType.equals("cheese")){pizza = new CheesePizza();}
    else{...}
    return pizza;
    }

    // 使用静态方式
    public static Pizza createPizza(String orderType){
    Pizza pizza = null;
    if(orderType.equals("greek")){pizza = new GreekPizza();}
    else if (orderType.equals("cheese")){pizza = new CheesePizza();}
    else{...}
    return pizza;
    }
    }

三、工厂方法模式

  • 将披萨项目的实例化功能抽象成抽象方法,在不同的口味点餐子类中具体实现
  • 定义了一个创建对象的抽象方法,由子类决定要实例化的类,将对象的实例化推迟到子类
    public abstract OrderPizza{
    Pizza pizza = null;
    String orderType;
    public OrderPizza(){
    do{
    orderType = getType();
    pizza = createPizza(orderType);// 抽象方法,由工厂子类实现
    pizza.prepare();
    pizza.bake();
    ...
    }while(...);
    }
    abstract Pizza createPizza(String orderType);
    }
    public class BJOrderPizza extends OrderPizza{
    Pizza createPizza(String orderType){
    if(orderType.equals("cheese")){pizza = new BJCheesePizza();}
    else if(orderTyope.equals("pepper")){pizza = new BJPepperPizza();}
    return pizza;
    }
    }
    public class LondonOrderPizza extends OrderPizza{
    Pizza createPizza(String orderType){
    if(orderType.equals("cheese")){pizza = new LondonCheesePizza();}
    else if(orderTyope.equals("pepper")){pizza = new LondonPepperPizza();}
    return pizza;
    }
    }
    // 调用方法
    main(){
    new BJOrderPizza();
    new LondonOrderPizza();
    }
    四、抽象工厂方法
  • 定义了一个interface用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  • 将简单工厂模式和工厂方法模式进行整合
  • 将工厂抽象成两层,AbsFactory(抽象工厂)和具体实现的工厂子类。开发人员可根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂变成了工厂簇,更利于代码的维护与扩展
    public interface AbsFactory{
    public Pizza createPizza(String orderType);
    }
    public class BJFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String orderType){
    Pizza pizza = null;
    if(orderType.equals("cheese")){pizza = new BJCheesePizza();}
    else if(...){...}
    return pizza;
    }
    }
    public class LondonFactory implements AbsFactory{
    ...// 同上
    }

public class OrderPizza{
AbsFactory factory;

public OrderPizza(AbsFactory factory){
    this.factory = factory;
}

public void setFactory(AbsFactory factory){
    Pizza pizza = null;
    String orderType = ...
    this.factory = factory;
    do{
        orderType = getType();
        pizza = factory.createPizza(orderType);
        if(pizza != null){
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }else{...}
    }while(...);
}
private String getType(){...}

}
// 调用方法
main(){
new OrderPizza(new BJFactory());
new OrderPizza(new LondonFactory());
}

JDK-Calendar源码中对于工厂模式的应用
main(){
Calendar cal = Calendar.getInstance();// 静态方法getInstance();
}
->
public static Calendar getInstance(){
return createCalendar(TimeZone.getDefault(),Local.getDefault(Local.Category.FORMAT));
}
->
private static Calendar createCalendar(TimeZone zone, Locale aLocale){
if(provider != null){
try{
return provider.getInstance(zone, aLocale)l// 默认方式获取
} catch(...){...}
Calendar cal = null;
if(aLocale.hasExtensions()){
String caltype = aLocale.getUnicodeLocaleType("ca");
if(caltype != null){
switch(caltype){
case "...": cal = new ...(); break;
case "...": cal = new ...(); break;
...
}
}
}
if(cal == null){
cal = new ...();
}
return cal;
}
}

五、原型模式
案例:克隆羊
传统方式:在客户端生成多个相同实例时,多次new对象,并将第一个new的对象的属性作为其他对象的属性值即可
缺点:
在创建新的对象时,需要重新获取原始对象的属性。对象复杂时效率低下
需要重新初始化对象,而不是动态地获取对象运行时的状态,不够灵活
改进思路:
Object类提供clone()方法。对于需要实现clone的Java类实现接口Cloneable即可
原型模式基本介绍:
- 用原型实例指定创建对象的种类,并通过拷贝这些原型,创建新的对象
- 允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
- 工作原理:将一个原型对象传给要发动创建的对象,要发动创建的对象通过请求原型对象拷贝他们自己来实施创建,即 对象.clone()
原型模式的结构:
Prototype:原型类,声明一个克隆自己的接口
ConcreatePrototype:具体的原型类,实现一个克隆自己的操作
Client:让一个原型对象克隆自己,以创建新的对象
代码实现:
public class Sheep implements Cloneable{
@Override // 使用默认的conle方法以克隆该实例
protected Object clone() throws ConeNotSupportedException{
Sheep sheep = null;
sheep = (Sheep)super.clone();// super.clone()返回Object,需要强转
return sheep;
}
}
// 调用方法
main(){
Sheep sheep1 = new Sheep("Tom", 1, "white");
Sheep sheep2 = (Sheep)sheep1.clone();
...
}
优点:如果Sheep中增删改了某些字段,调用方式不需要修改

Spring框架中的应用:Bean的创建

// scope="prototype"表示用原型模式创建bean, 也可以用单例模式创建bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object bean1 = applicationContext.getBean("id01");
Object bean2 = applicationContext.getBean("id01");

原型设计模式中的浅拷贝模式
修改Sheep类,添加一个private Sheep friend;的类
Sheep sheep1 = new Sheep("Tom", 1, "White");
sheep1.friend = new Sheep("Jack", 2, "Black");
Sheep sheep2 = (Sheep)sheep1.clone();
...
sheep2只是和sheep1共用了同一个堆内存空间

原型设计模式中的深拷贝模式
深拷贝的实现方式:重写深拷贝方式、序列化
// 1.当类的成员变量只有普通变量时
public class DeepCloneableTarget implements Serializable, Cloneable{
private static final long serialVersionUID = 1L;
private String cloneName;
private String cloneClass;

public DeepCloneabelTarget(String cloneName, String cloneClass){
    this.cloneName = cloneName;
    this.cloneClass = cloneClass;
}

@Override
protected Object clone() throws ClonNotSupportedException{
    return super.clone();
}   

}
// 2.当类的成员变量中有引用类型的成员变量时
public class DeepPrototype implements Serializable, Cloneable{
public String name;
public DeepCloneableTarget deepCloneableTarget;// 引用类型的属性

public DeepPrototype(){super();}
// 实现深拷贝-方式一:重写clone()
@Override
protected Object clone() throws CloneNotSupportedException{
    Object object = null;
    // 完成对基本数据类型(属性)的克隆
    deep - super.clone();
    // 单独处理引用类型变量的克隆
    DeepPrototype deepProrotype = (DeepProrotype)object;
    deepProrotype.deepCloneableTraget = (DeepCloneableTarget)deepCloneableTarget.clone();
    return deepProrotype;
}
// 实现深拷贝-方式二:通过对象的序列化实现(推荐)
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);
        ois.readObject();
        DeepPrototype copyObject = (DeepPrototype)ois.readObject();
        return copyObject;
    }catch(Exception e){...}
}

}
// 调用方法
main(){
DeepPrototype object = new DeepProrotype();
object.name = "eric";
object.deepCloneableTarget = new DeepCloneableTarget("", "");
// 方式一的调用方式
DeepProrotype objectNew = (DeepPrtotype)object.clone();
// 方式二的调用方式
DeepProrotype objectNew = (DeepPrtotype)object.deepClone();
}
原型模式的缺点:对于已开发的类,需要编写深拷贝或浅拷贝的克隆方法,违背ocp原则

六、建造者模式
案例:需要建房(打桩、砌墙、封顶)。房子虽然流程相同,但细节不同。对于不同房子的打桩方式不同,砌墙方式不同,封顶也不同。变成实现建房流程
// 传统实现代码
public abstract class AbstractHouse{
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
public void build(){
buildBasic();
buildWalls();
roofed();
}
}
public class CommonHouse extends AbstractHouse{
// 实现父类方法
...

}
// 调用代码
main(){
CommonHouse commonHouse = new CommonHouse();
common.build();
}
缺点:没有设计缓存层,把产品(房子)和创建产品的过程(build())封装在了一起,耦合性增强了
解决方案:将产品和产品建造过程解耦,即使用建造者模式

建造者模式(生成器模式)基本介绍
- 将复杂对象的建造过程抽象出来,使抽象过程的不同实现方法可以构造出不同表现(属性)du对象
- 一步步创建复杂的对象,允许用户只通过指定复杂对象的类型和内容就可以构建它们。用户不需要知道内部的具体构建细节
建造者模式的四个角色
- Product:一个具体的产品对象
- Builder(抽象建造者):创建一个Product对象的部件指定的接口/抽象类,只关心建造流程,不关心建造细节
- ConcreteBuilder(具体建造者):实现接口,构建和装配各个部件,关心建造细节
- Director(指挥着):构建一个使用Builder接口的对象,主要是用于创建一个复杂的对象。主要有两个作用:1.隔离了用户与对象的生产过程;2.负责控制产品对象的生产过程
建造者模式的代码实现:
public class house{
private String basic;
private String walls;
private String roof;
// getter&setter
...
}
public abstract class HouseBuilder{
House house = new House();// 组合关系
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
public House build(){
return house;
}
}
public class CommonHouse extends HouseBuilder{
// 实现父类的抽象方法
...
}
public class HouseDirector{// 动态指定制作流程,返回具体的产品实例
HouseBuilder houseBuilder = null;
// 方法一:通过构造器传入聚合关系中的实例
public HouseDirector(HouseBuilder houseBuilder){
this.houseBuilder = houseBuilder;
}
// 方法二:通过set方法传入聚合关系中的实例
public void setHouseBuilder(HouseBuilder houseBuilder){
this.houseBuilder = houseBuilder;
}
public House constructHouse(){
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
// 调用方法
main(){
// 盖普通房子
HouseDirector houseDirector = new HouseDirector(new CommonHouse);
House house = houseDirector.constructHouse();
}

建造者模式在jdk源码中的应用:StringBuilder
StringBuilder的构造器中涉及到的角色:
Appendable接口定义了多个append方法(抽象方法),即为抽象建造者
建造者具体的实现类:AbstractStringBuilder(是建造者,只是不能实例化)
StringBuilder既为指挥者,也是具体的建造者(继承了AbstractStringBuilder中的建造方法并执行。因为父类是抽象类不能真正执行)
public final class StringBuilder extends AbstractStringBuilder{
... // 其他方法
@Override
public StringBuilder append(String str){// 实现的父类中的append方法
super.append(str);
return this;
}
...
}
abstract class AbstractStringBuilder implements Appendable, CharSequence{
...
public AbstractStringBuilder append(String str){
...// 字符串的建造过程
}
...
}
public interface Appendable{
...// 此处声明了一些建造字符串的方法
}

########结构型模式(把类或对象结合在一起形成一个更大的结构)
七、适配器模式(又名包装器 Wrapper)

  • 将某个类的接口转成客户端期望的另一个接口表示,为了实现兼容性
  • 从用户的角度看不到被适配者,是解耦的
  • 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
    1.类适配器模式
    基本介绍:Adapter类,通过继承src类,实现dst类接口,完成src->dst的适配
    public class Voltage220V {
    public int output220V() {
    int src = 220;
    sout("电压"+src+"V");
    return src;
    }
    }
    public interface IVoltage5V{
    public output5V();
    }
    // 类适配器
    public class VoltageAdapter extends Voltage220V, implements Voltage5V{
    @Override
    public int output5V() {
    // 获取到220V电压
    int srcV = output220V();
    int dstV = ...; // 将220V电压转换成5V电压
    return dstV;
    }
    }
    public class Phone{
    public void charging(IVoltage5V iVoltage5V){
    if (iVoltage5V.output5V() == 5) {
    sout("电压5V,可以充电");
    } else if (...) {...} else {...}
    }
    }
    main(){
    Phone phone = new Phone();
    phone.charging(new VoltageAdapter());
    }
    注意事项:
    1)Java是单继承机制,所以类适配器需要继承src类,这是一个缺点,因为需要dst必须是接口,有一定局限性
    2)src类的方法在Adapter中会暴露出来,也增加了使用的成本
    3)由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了

2.对象适配器模式(针对类适配器的继承缺陷进行了改进)
1)基本思路和类的适配器模式相同,只是将Adapter类作修改,不是继承src类,而是持有src类的实例,以解决兼容性的问题。即持有src类,实现dst类接口,完成src->dst的适配
2)根据"合成复用原则",在系统中尽量使用关联关系来替代继承关系
3)对象适配器模式是适配器模式常用的一种
public class Voltage220V {
public int output220V() {
int src = 220;
sout("电压"+src+"V");
return src;
}
}
public interface IVoltage5V{
public output5V();
}
// 对象适配器
public class VoltageAdapter implements Voltage5V{
private Voltage220V voltage220V; // 关联关系中的聚合关系
// 通过构造器传入
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dst = 0;
if (null != voltage220V) {
int src = voltage220V.output220V();
sout("使用对象适配器进行转换");
dst = src/44;
sout("适配完成");
}
return dst;
}
}
public class Phone{
public void charging(IVoltage5V iVoltage5V){
if (iVoltage5V.output5V() == 5) {
sout("电压5V,可以充电");
} else if (...) {...} else {...}
}
}
main(){
Phone phone = new Phone();
phone.charging(new VoltageAdapter(new Voltage220V()));
}
注意事项:
1)对象适配器和类适配器思想相同,实现方式不同,遵循了合成复用原则,使用组合关系替代继承关系,解决了类适配器必须继承src的问题,也不再要求dst必须是接口
2)使用成本更低,更灵活

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

适配器模式在Java中的应用-SpringMVC(HandlerAdapter)
复习:请求的处理流程
Client发起请求->DispatcherServlet中调用HandlerMapping(一个控制器)->找到对应的处理器,处理器执行目标方法->通过对应的适配器调用控制器(Controller)并返回ModelAndView对象->...
在寻找对应的处理器中,应用了适配器
proteted void doDispatch (HttpServletRequest request, HttpServletResponse response) {
...
mapperHandler = getHandler(processedRequest); // 拿到控制器(Controller)
HandlerAdapter ha = getHandlerAdapter(mapperHandler.getHandler()); // 使用适配器
...
}
// ha穿透后的代码
protected HandlerAdapter getHandlerAdapter(Object handler) throws ... {
...
for循环HandlerAdapter中所有适配器,找到支持入参handler的适配器并返回
}
// 适配器接口
public interface HandlerAdapter{

}
作用:扩展Controller时,只需增加一个适配器类就完成了SpringMVC的扩展了(使用方不需要变化,比如新增了一个OtherController实现了Controller,只需再新增一个实现了HandlerAdapter的OtherHandlerAdapter即可)

三种适配器的区分:
1)类适配器:在Adapter中,将src当做类,继承
2)对象适配器:在Adapter中,将src当做对象,通过组合关系,持有被适配的类的对象
3)接口适配器:在Adapter中,将src当做接口,通过实现关系,实现接口

八、桥接模式(分为抽象层和实现层)
案例:手机操作问题
传统继承方式进行模式设计时,存在的问题
1)扩展性问题(类爆炸)。如果我们要增加手机的样式,需增加各品牌手机的类;需增加手机品牌时,也需在各个手机样式类下增加
2)违反了单一职责原则。增加手机样式时,需同时增加所有品牌的手机,增加了代码维护成本
3)解决方案-使用桥接模式
基本介绍:
1)桥接模式,将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
2)是一种结构型设计模式
3)基于类的最小设计原则。通过使用封装、聚合及继承等行为,让不同的类承担不同职责。由于抽象与行为实现分离开了,可以保持各部分的独立性以及对应他们的功能。
abstract class Abstraction {
// 抽象类,维护了Implementor,即Implementor的实现类ConcreteImpolementorA。
// Abstraction与Implementor是聚合关系,Abstraction充当桥接类
}
RefinedAbstraction{
// 是Abstraction抽象类的子类
}
public class Implementor{
// 行为实现类的接口
}
ConcreteImplementorA{
// 行为具体的实现类A
}
ConcreateImplementorB{
// 行为具体的实现类B
}

// 桥接模式实现
public interface Branc {
void open();
void close();
void call();
}
public class XiaoMi implements Brand {
@Override
public void open() { sout("小米手机开机"); }
@Override
public void close() { sout("小米手机关机"); }
@Override
public void call() { sout("小米手机打电话"); }
}
public class Vivo implements Brand {
... // 同上
}
public abstract class Phone {
private Brand brand;
public Phone(Brand brand) {
super();
this.brand = brand;
}
protected void open() { this.brand.open(); }
protected void close() { this.brand.close(); }
protected void call() { this.brand.call(); }
}
public class FoldedPhone extends Phone {
// 构造器
public FoldedPhone(Brand brand) {
super(brand);
}
public void open() {
super.open();
sout("折叠手机开机");
}
public void close() {
super.close();
sout("折叠手机关闭");
}
public void call() {
super.call();
sout("折叠手机打电话");
}
}
main() {
Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
}
如果按照传统模式,如果新增了一种手机样式,还需要增加这种手机样式对所有品牌手机的的定义(e.现有这些定义的类:折叠的小米手机、折叠的vivo手机、折叠的华为手机、滑盖的小米手机、滑盖的vivo手机、滑盖的华为手机,如果新增了一种触屏手机,由于传统模式中,品牌继承自手机样式,手机样式继承自手机这个基类,所以还需要新定义触屏的小米手机、触屏的vivo手机、触屏的华为手机。这样类爆炸且维护成本高)。按照桥接模式,新增的手机样式只需要继承自桥接类,重写或实现桥接类中的一些抽象或非抽象方法,以实现新手机样式概念的定义即可,而不需新增手机品牌。
在类的定义过程中,桥接类通过聚合关系,将约定的行为模式传入,这样桥接类作为抽象类,其子类在实现时也需维护桥接类中的行为模式,达到行为概念的复用。

桥接模式在Java中的应用:JDBC(Driver接口)
常见应用场景:对于不希望使用继承或因多层次继承导致系统类的个数急剧增加的系统
JDBC驱动程序
银行转账系统(转账分类:网上转账、柜台转账、ATM转账;转账用户类型:普通用户、银卡用户、金卡用户)
消息管理系统(消息类型:即时消息、延时消息;消息分类:手机短信、邮件消息、QQ消息..)

九、装饰者模式
案例:星巴克咖啡订单项目
需求:
1)咖啡种类/单品咖啡:意式、ShortBlack、LongBlack(美式)、Decaf(无因)
2)调料:Milk、Soy(豆浆)、Chocolate
3)要求在扩展新的咖啡种类时,具有良好的扩展性、改动方便、维护方便
4)使用OO来计算不同种类咖啡的费用:客户可以点单品咖啡,也可以点单品咖啡+调料
方案一:
设计一个Drink的类,每种具体的单品咖啡都继承Drink,每种"单品+调料"的组合也会生成新的类继承Drink类,这样导致每新增一种单品或调料,都会产生新的排列组合(如果当前已定义m种单品与n种调料,每新增一种单品,需新定义n种类;每新增一种调料,需新定义m种类),这样会导致类爆炸。

方案二:
设计一个Decaf类,提供cost()方法。子类Drink继承Decaf后,将调料内置到Drink中进行计算,控制了类的数量,类过多的情况解决。但依然存在以下问题:
1)增删调料种类时,代码的维护量还是很大。
2)考虑到用户可以添加多份调料时,可以将hasMilk返回对应的int

方案三(装饰者模式):
定义:装饰者模式指,动态地将新功能附加到对象上,在对象功能扩展方面,它比集成更有弹性,体现了OCP原则
1)装饰者模式就像打包快递:
主体(被装饰者):陶瓷、衣服
包装(装饰者):报纸填充、塑料泡沫、纸板、木板
2)Component(装饰者和被装饰者都是component)
3)ConcreteComponent和Decorator
ConcreteComponent:具体的主体,比如单品咖啡
Decorator:装饰者,比如各种调料
4)如果ConcreteComponent类很多,还可以设计一个缓冲层(位于ConcreteComponent与Component之间),将共有的部分提取出来,抽象成一个类

实现方法:
1)Drink类作为抽象类
2)Coffee是缓冲类
3)Decorator是一个装饰类,含有被装饰对象
4)Decorator的cost方法进行一个费用的叠加计算,递归地计算价格
ps:由于装饰者继承了Drink,所以带有调料的饮品依然属于Drink,还可以再注入到新的装饰者类中。这样不管什么形式的单品+调料的组合,都可以通过递归方便地进行组合与维护
// 代码实现
@Setter
@Getter
// 抽象类(本体,被装饰者)
public abstract class Drink {
public String description;
private double price = 0.0;
// 用于计算价格的方法,需子类实现
public abstract double cost();
}
public class Coffee extends Drink {
@Override
public double cost() {
return super.price;
}
}
public class Espresso extends Coffee {
public Espresso () {
setDescription("意大利咖啡");
setPrice(12.0);
}
}
public class LongBlack extends Coffee {
public LongBlack() {
setDescription("美式咖啡");
setPrice(8.0);
}
}
public class ... extends Coffee { ... }
// 装饰类
public class Decorator extends Drink {
private Drink drink;
// 通过构造器,实现组合关系
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public double cost() {
return super.getPrice()+ drink.cost();
}
@Override
public String getDescription() {
// 此处super可省略,因为子类里面的description和getPrice()实际就是父类中的方法
return super.description+" "+super.getPrice()+" && "+drink.getDEscription();
}
}
// 装饰者的实现类,是具体的调味品
public class Chocolate extends Decorator {
// 重写父类的构造器
public Chocolate(Drink drink) {
super(drink);
setDescription("巧克力");
setPrice(5.0);
}
}
public class Milk extends Decorator {... // 同上}

main() {
// 1.先点一份单品咖啡
Drink order = new LongBlack();
// 2.加入调料
order = new Milk(order);
// 3.再加入另一份调料
order = new Chocolate(order);
}

在Java中的应用-IO(FilterInputStream)
main() {
// 1.InputStream是抽象类,是被修饰者
// 2.FileInputStream是InputStream的子类,是被修饰者的一个具体实现
// 3.FilterInputStream是InputStream的子类,是Decorator
// 4.DataInputStream是FilterInputStream子类,是具体的修饰者
// 5.FilterInputStream类包含有被修饰者的实例
DataInputStream dis = new DataInputStream(new FileInputStream("path"));
sout("...");
dis.close();
}
public class FileInputStream extends InputStream {}
public class FilterInputStream extends InputStream {
protected volatile InputStream in; // 被装饰者
}
public abstract class InputStream implements Closeable {}

十、组合模式(本分整体模式)
案例:学校院系展示
除了要展示学校下的学院信息、学院下的系信息,同时要保证三种类之间要有学校的类能操作学院的类,学院的类能操作系,但是系不需要操作其他组织机构。
传统方案:
1)将学院看做学校的子类,系看成学院的子类,实际是站在组织的角度
2)实际上,我们的需求是展示出学校下有多个学院,学院下有多个系
解决方案:把学校、学院、系都看做是组织结构,他们之间没有继承关系,而是树形结构(组合模式)

基本介绍
1)创建了对象组的树形结构,将对象组合成树结构以表示"整体-部分"的层次关系
2)使得用户对单个对象和组合对象的访问具有一致性,即组合能让客户以一致的方式处理个别对象以及组合对象
原理类图元素说明
1)Component:是组合中对象生命接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理Component子部件。Component可以是抽象类或接口
2)Leaf:在组合中表示叶子节点,没有子节点
3)Composite:非叶子节点,用于存储子部件。在Component接口中实现子部件的相关操作(e.增删改查操作),但可能不存在叶子节点的一些性质。充当了管理者
常见应用场景:
1)当处理的对象为树形结构,需对树上的节点和叶子节点操作时,使用组合模式可以提供一致的方式操作元素,不用考虑节点是否是叶子节点

// 组合模式在案例中的代码实现
@Getter
@Setter
public abstract class OrganizationComponent {
private String name;
private String description;
protected void add(OrganizationComponent organizationComponent) {
// 这里可以写一个默认实现
}
protected void remove(OrganizationComponent organizationComponent) {...}
public OrganizationComponent(String name, String description) {
super();
this.name = name;
this.description = description;
}
protected abstract void print(); // 抽象方法待子类实现
}
// University就是组合模式中的Composite,可以管理College
public class University extends OrganizationComponent {
List OC = new ArrayList<>();
public University(String name, String description) {
super(name, description);
}
@Override
protected void add(OrganizationComponent oc) {
OC.add(oc);
}
@Override
protected void remove(OrganizationComponent oc) {
OC.remove(oc);
}
@Override
public STring getName(){ return super.getName(); }
@Override
public String getDescription() { return super.getDescription(); }
@Override
protected void print() {
// 输出University包含的学院信息
sout("学校名称:"+getName());
for (OrganizationComponent oc : OC){
oc.print();
}
}
}
public class College extends OrganizationComponent {
List OC = new ArrayList<>();
// 同上,只修改print()
@Override
protected void print() {
sout("学院名称:"+getName());
for (Organization oc : OC) {
oc.print();
}
}
}
public class Department extends OrganizationComponent {
// add()、remove()方法不用写了,以及不需要注入OrganizationComponent了
@Override
protected void print() {
sout("系名称:"+getName());
}
}
main() {
// 创建对象
OrganizationComponent university = new University("清华大学", "北京");
OrganizationComponent college1 = new College("计算机学院", "理工科");
OrganizationComponent college2 = new College("信息工程学院", "理工科");
college1.add(new Department("软件工程","计科"));
college1.add(new Department("网络工程","计科"));
college1.add(new Department("计算机信息安全","计科"));
college2.add(new Department("通信工程","电子");
college2.add(new Department("信息工程","电子")));
university.add(college1);
university.add(college2);
// 都使用相同的pring方法,但
sout(university.print());
sout(college1.print());
sout(college2.print());
}
组合模式的应用:对于控件的层级结构、组织机构的层级结构
在JDK集合的应用:HashMap

十一、外观模式(过程模式)
案例:影院管理项目
传统方式:
1)创建相关的对象
2)调用创建的各对象的方法(客户端直接依赖了对象)
问题:
1)造成调用过程混乱,没有清晰的过程(调用的一系列行为是一个流程,调用的流程)
2)不利于在客户端中维护子系统的操作
3)定义一个高层接口,给子系统中的一组接口提供一个一致的界面,用来访问子系统中的一群接口,即通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无需关心这个子系统的内部实现

// 代码实现
public class DVDPlayer {
// 这里是饿汉式的单例模式写法
private static DVDPlayer instance = new DVDPlayer();
public static DVDPlayer getInstance() { return instance; }
method1();
method2();
...
}
public class Popcorn {
private static Popcorn instance = new Popcorn();
public static Popcorn getInstance() { return instance; }
method1();
method2();
...
}
... // 其他子系统类
// 外观类
public class HomeTheaterFacade {
private Popcorn popcorn;
private DVDPlayer dvdPlayer;
...
public HomdeTheaterFacade() {
super();
this.dvdPlayer = DVDPlayer.getInstance();
this.popcorn = Popcorn.getInstance();
...
}

// 提供给客户端的封装的方法
public void ready() {
    dvdPlayer.on();
    popcorn.on();
    ...
}
public void play() {
    dvdPlayer.play();
    ...
}
public void end() {
    dvdPlayer.off();
    popcorn.off();
    ...
}

}
main() {
HomeTheaterFacade htf = new HomeTheaterFacade();
htf.ready();
htf.play();
htf.end();
}

我项目中的应用:生成周期计划、生成工单与年度检修计划的接口
JDK中外观模式的应用-Mybatis

十二、享元模式(蝇量模式)
案例:展示网站项目需求(网站信息可能以网站、新闻、公众号的形式发布)
问题分析:需要的网站结构相似度很高,且都不是高访问量网站。如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,造成服务器资源浪费
解决思路:整合到一个网站中,共享其相关的代码和数据。对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器开支。这样,对于代码来说,由于是一份实例,维护和扩展都更加容易。

基本介绍:
1)运用共享技术有效地支持大量细粒度对象
2)常用于系统底层开发,解决系统的性能问题(e.数据库连接池,里面都是创建好的连接对象。对于存在的对象,我们直接使用现存的连接对象;如果没有,则创建一个)
3)享元模式能够解决重复对象的内存浪费问题。当系统中有大量相似对象,需要缓冲池时,不需总是创建新的对象,可以从缓冲池里拿。这样可以降低系统内存消耗,提高效率
4)String常量池、数据库连接池、缓冲池等都是享元模式的jdk应用。享元模式是池技术的重要实现方式

享元模式的原理类图:
1)FlyWeight是抽象的享元角色,是产品的抽象类,同时定义出对象的外部状态(变化相对较多的状态)和内部状态(相对较稳定的状态)的接口或实现。
2)ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色定义的相关业务。
3)UnSharedConcreteFlyweight是不可共享的角色,一般不会出现在享元工厂中(FlyweightFactory)
4)FlyWeightFactory享元工厂类,用于构建一个池容器(集合),同时提供从池中获取对象的相关方法
ps:用唯一标识码判断“元”是否存在。常用HashMap/HashTable存储
享元模式提高了系统的复杂度,将内部状态与外部状态分离

享元模式的内部状态与外部状态:
1)享元模式提了两个要求:细粒度与共享对象。需将对象的信息分为内部转态与外部状态两部分
2)内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的变化而改变
3)外部状态指对象得以依赖的一个标记,是随环境而改变的、不可共享的状态
4)举例:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生。因内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏。如果用享元模式来处理,仅需两个棋子对象实例即可,解决了对象的开销问题。

// 代码实现
public abstract class WebSite {
public abstract use();
}
public class ConcreteWebSite extends WebSite {
private String type = ""; // 网站发布的类型
@Override
public void use(User user) {
sout("网站的发布形式为" + type + "在使用中,使用者是" + user.getName());
}
public ConcreteWebSite (String type) {
this.type = type;
}
}
public class WebSiteFactory {
// 网站工厂类,根据需求返回一个具体的网站实例
// 集合,网站池
private HashMap pool = new HashMap<>{};
// 根据网站类型,返回网站。如果没有就创建网站,放入池中后再返回
public WebSite getWebSiteCategory(String type) {
if (!pool.containsKey(type)) {
pool.put(type, new ConcreteWebSite(type));
}
return (WebSite) pool.get(type);
}
// 获取网站分类的种数(池中有多少个网站类型)
public int getWebSiteCount() {
return pool.size();
}
}
main() {
WebSiteFactory wsFactory = new WebSiteFactory();
WebSite webSite1 = wsFactory.getWebSiteCategory("新闻");
webSite1.use(new User("Tom"));
WebSite webSite2 = wsFactory.getWebSiteCategory("博客");
webSite2.user(new User("Tomas"));
WebSite webSite3 = wsFactory.getWebSiteCategory("博客");
webSite3.user(new User("Leo"));
}
// 到此我们发现,如果多个用户获取相同类型的网站,此时使用了相同网站的用户信息是不通的,所以需要创建一个外部状态类
@Data
public class User {
private String name;
public User(String name) {
this.name = name;
}
}

// 享元模式在JDK中的应用-Integer
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// 1.在valueOf()方法中,先判断值是否在IntegerCache中。如果不在,就创建新的Integer
// 2.valueOf方法,使用到享元模式
// 3.如果使用valueOf方法得到一个Integer实例,范围在-128-127,执行速度比new更快
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
        sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
        try {
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
        } catch( NumberFormatException nfe) {
            // If the property cannot be parsed into an int, ignore it.
        }
    }
    high = h;

    cache = new Integer[(high - low) + 1];
    int j = low;
    for(int k = 0; k < cache.length; k++)
        cache[k] = new Integer(j++);
    // 如果范围在[-128, 127]
    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
}

private IntegerCache() {}

}

十三、代理模式

########行为型模式(类和对象如何交互,及划分职责和算法)

问题合集:
1.为什么类适配器的目标(dst)必须是接口,而对象适配器的目标可以是类?
2.spring结构中,Controller、Service、Mapper层的设计,是不是就是典型的外观模式应用场景?

你可能感兴趣的:([设计模式]二十三种设计模式)