23种设计模式的介绍
类与类之间的6种关系
在学习设计模式之前需要类与类之间的6种关系,分别是泛化,实现,依赖,关联,聚合,组合
泛化关系
泛化关系是依赖关系的一种特例,就是两类之间的继承,就是泛化关系
实现关系
实现关系是指实现接口,一个类实现一个接口,就叫做实现
依赖关系
只要两个类之间用到了对方,就叫做依赖关系,具体可以表现为:
- 一个类是另一个类的成员变量
- 一个类是另一个类中方法的返回值
- 一个类是另一个类中的成员变量
- 一个类是另一个类中方法的参数
- 一个类是另一个类中的局部变量
关联关系
关联关系是类与类之间的联系,具有单向关系和双向关系
聚合关系
聚合关系是指整体和部分的关系,整体和部分是可以相互分离的,是关联关系的一种特例
组合关系
组合关系是指整体和部分的关系,整体和部分是不可以相互分离的,也是关联关系的一种特例
设计模式的原则
在学习设计模式之前,所有的设计模式都需要遵守的设计模式的原则有6个,分别是:单一职责原则,接口隔离原则,依赖倒转原则,开闭原则,里氏替换原则,迪米特法则(最少知道原则)
单一职责原则
单一职责简单来说就是每一个类或者接口都只完成一个功能,这样不会再后面扩展时修改原始代码,只需要重新添加具体功能即可。
接口隔离了原则
当子类实现的接口中有很多的方法,但是在子类中又用不到这么多的方法,所以重写接口中的所有方法中会造成很多的冗余方法,因此需要将接口中的方法进行拆分,分成更小的接口
依赖倒转原则
依赖倒转原则的核心思想就是面向接口编程,依赖关系传递的三种方式分别为
- 接口传递
- 构造方法传递
- setter方法传递
开闭原则
很抽象,核心思想是对扩展开放对修改关闭
里氏替换原则
里氏替换原则是为了降低耦合度的,当类之间一旦有了继承关系,就会增加了耦合度,因此在想使用父类的方法,并且还需要扩展父类的功能时,可以不使用继承的方法,利用类与类之间的依赖关系,比如将原来的父类变成子类的成员变量,可以实现功能的扩展
迪米特原则
又叫做最少知道原则,是与直接的朋友通信,称出现在一个类中作为这个类的成员变量,方法参数或者方法的返回值的类,叫做朋友,而出现在局部变量中的类不算是朋友
23种设计模式
单例模式
就是整个项目中只能创建出一个实例化对象,因此要求构造器私有化,不能所有程序调用都可以实例化,还要提供外界的方法,用来调用实例化的唯一一个对象
饿汉模式的单例模式
故名思意,饿汉模式就是不管你用不用对象都创建出来对象,供你使用,分为两类
- 静态变量的饿汉模式,把实例化的对象修饰成变量的形式
package com.njupt.single;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/08/19:15
* @Description:饿汉模式,不管你要不要,都创造出来等你用
*/
public class Hungry {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
//结果为true说明是同一个对象。
System.out.println(singleton1==singleton2);
}
}
//采用静态变量的方式实现饿汉模式
/*
* 优点:在类加载是就完成了实例化,避免了线程同步问题
* 缺点:在类加载是创建出实例化对象,如果没有使用会造成内存的浪费
* */
class Singleton{
private static Singleton singleton=new Singleton();
private Singleton(){
}
public static Singleton getSingleton(){
return singleton;
}
}
- 写在静态代码块中的单例模式
package com.njupt.single;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/08/19:15
* @Description:饿汉模式,不管你要不要,都创造出来等你用
*/
public class Hungry {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
//结果为true说明是同一个对象。
System.out.println(singleton1==singleton2);
}
}
//采用静态变量的方式实现饿汉模式
/*
* 优点:在类加载是就完成了实例化,避免了线程同步问题
* 缺点:在类加载是创建出实例化对象,如果没有使用会造成内存的浪费
* */
class Singleton{
private static Singleton singleton;
private Singleton(){
}
static{
singleton=new Singleton();}
public static Singleton getSingleton(){
return singleton;
}
}
懒汉模式的单例模式
思想是什么时候调用方法,什么时候创建出来唯一的实例化对象给调用者使用
- 线程不安全的懒汉模式
package com.njupt.single;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/08/19:01
* @Description:
*/
public class Lazy {
public static void main(String[] args) {
Single single = Single.getSingle();
Single single1 = Single.getSingle();
System.out.println(single==single1);
}
}
/*优点:什么时候使用,什么时候才开始创建实例化对象
* 缺点:线程不安全的懒加载,当多个线程使用时,会造成线程安全问题
* */
class Single{
private Single(){
}
private static Single single;
public static Single getSingle(){
if(single==null){
single=new Single();
}
return single;
}
}
- 最简单的线程安全的懒汉式,但是效率太低。
package com.njupt.single;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/08/19:01
* @Description:
*/
public class Lazy {
public static void main(String[] args) {
Single single = Single.getSingle();
Single single1 = Single.getSingle();
System.out.println(single==single1);
}
}
/*优点:什么时候使用,什么时候才开始创建实例化对象,并且是线程安全的
* 缺点:synchronized同步代码块锁的粒度太大,性能较差效率低
* */
class Single{
private Single(){
}
private static Single single;
public static synchronized Single getSingle(){
if(single==null){
single=new Single();
}
return single;
}
}
- 双重校验锁
package com.njupt.single;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/08/19:01
* @Description:
*/
public class Lazy {
public static void main(String[] args) {
Single single = Single.getSingle();
Single single1 = Single.getSingle();
System.out.println(single==single1);
}
}
/*优点:什么时候使用,什么时候才开始创建实例化对象,并且是线程安全的
*推荐使用。。。
* */
class Single{
private Single(){
}
//volatile关键字禁止指令重排,因为在创建对象时有三步
/*
* 1.分配内存
* 2.初始化对象
* 3将分配的内存指向初始化对象
*
* 不加volatile会出现的问题
* 如果两个线程同时进入了同步代码块,当第一个线程抢到锁然后结束之后,
* 此时可能会先执行1.3两步,没有初始化对象,也就是此时single可能还为null,当第二个线程进入锁后
* 判断single仍然为空,此时会在创建一个对象,就不满足单例模式的要求了,因此需要volatile关键字来禁止指令重排。
*
* */
private static volatile Single single;
public static Single getSingle(){
if(single==null){
synchronized (Single.class){
if(single==null){
single=new Single();
}
}
}
return single;
}
}
- 静态内部类的懒加载
利用了静态内部类的特点:- 外部类加载的时候,其静态内部类不会立马被加载
- 在调用方法时,用到了静态内部类的变量之后,静态内部类才会被加载
package com.njupt.single;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/08/21:13
* @Description:利用静态内部类来实现懒汉模式的单例
*/
public class StaticInnerClass {
public static void main(String[] args) {
SingleLi singe = SingleLi.getSinge();
SingleLi singe2 = SingleLi.getSinge();
System.out.println(singe==singe2);
}
}
//静态内部类
class SingleLi{
//构造方法
private SingleLi(){
}
//静态内部类
private static class InnerSingle{
private static SingleLi singleLi=new SingleLi();
}
public static SingleLi getSinge(){
return InnerSingle.singleLi;
}
}
利用枚举实现单例模式
简单工厂模式
简单工厂模式是属于创建型模式,是工厂模式的一种,简单工厂模式是由一个工厂决定创建出哪一个产品类的实例,简单工厂模式是工厂模式家族中最简单使用的模式。
核心思想是定义一个创建对象的类,由这个类来封装实例化对象的行为。
在软件开发中,当我们会用到大量的创建某种或者某类或者某批对象时,就会使用到工厂模式。
package com.njupt.simpleFactory;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/09/17:24
* @Description:简单工厂先出现一个抽象的父类,利用多态模式实现简单的工厂模式
*/
//先建立一个抽象的类,让其他的类来继承
public abstract class Operation {
public abstract void doMath(int i,int j);
}
class Add extends Operation{
@Override
public void doMath(int i, int j) {
System.out.println("相加之后的结果为:"+(i+j));
}
}
class Sub extends Operation{
@Override
public void doMath(int i, int j) {
System.out.println("相减之后的结果:"+(i-j));
}
}
class Multiple extends Operation{
@Override
public void doMath(int i, int j) {
System.out.println("两数相乘之后:"+(i*j));
}
}
class Div extends Operation{
@Override
public void doMath(int i, int j) {
System.out.println("两数相除之后:"+(i/j));
}
}
//简单工厂,开始处理客户端传入的数据进行处理
class Factory{
public static void startMath(int i,int j,String sym){
Operation operation;
if(sym.equals("+")){
operation=new Add();
operation.doMath(i,j);
}else if(sym.equals("-")){
operation=new Sub();
operation.doMath(i,j);
}else if(sym.equals("*")){
operation=new Multiple();
operation.doMath(i,j);
}else if(sym.equals("/")){
operation=new Div();
operation.doMath(i,j);
}else {
System.out.println("输入的符号有误!");
}
}
}
客户端的测试代码
package com.njupt.simpleFactory;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/09/17:43
* @Description:
*/
public class OperationTest {
public static void main(String[] args) {
Factory.startMath(3,4,"+");
Factory.startMath(4,3,"-");
Factory.startMath(3,4,"*");
Factory.startMath(4,2,"/");
}
}
结果:
相加之后的结果为:7
相减之后的结果:1
两数相乘之后:12
两数相除之后:2
Process finished with exit code 0
工厂方法模式
抽象工厂模式
原型模式
原型模式是指:用原型实例创建出对象的种类,并且通过拷贝这些原型,创建新的对象,是一种创建型对象,简单的可以理解为复制出和原来对象一样的,即孙悟空拔出猴毛变成其他猴子一样。
工作原理是:通过一个原型对象传给那个要发动创建的对象,通过请求原型对象拷贝他们自己来创建一个一样的对象,即对象.clone().
好处:如果原始对象发生变化,其他克隆对象会发生相应的变化,无需修改代码。
缺点:需要为每一个类都写一个克隆方法,对原始类来说不难,但是要修改克隆之后的类时,需要修改其原始的类,就是要修改源码,违背了ocp原则。
一定要实现Cloneable接口才可以进行克隆
package com.njupt.prtoTypeMode;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/09/19:44
* @Description:原型模式
*/
public class Monkey implements Cloneable{
private String name;
private String color;
private String weapon;
private int age;
public Monkey() {
}
public Monkey(String name, String color, String weapon, int age) {
super();
this.name = name;
this.color = color;
this.weapon = weapon;
this.age = age;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", weapon='" + weapon + '\'' +
", age=" + age +
'}';
}
//原型模式要重写clone方法
@Override
protected Object clone() {
Monkey monkey=null;
try {
monkey= (Monkey) super.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return monkey;
}
}
在spring框架中的xml配置bean标签的scope时,会使用到原型模式:prototype,此时就可以将实例化对象设置成原型模式,当有新的属性时,会和原来的类信息一样,但是不是一个类,因为不是单例模式。
原型设计模式的浅拷贝和深拷贝
- 浅拷贝
浅拷贝:如果在进行clone过程中拷贝的是基本数据类型,就会进行值传递,如果拷贝的是引用数据类型,就会进行引用(内存地址)传递,就是将该成员的引用值(内存地址)复制一份给新的对象。例如,如果孙悟空的儿子属性,就会也是一个猴子属性,在进行原型模式进行克隆时,对于引用数据类型就是内存地址传递(引用传递)。
public class Monkey implements Cloneable{
private String name;
private String color;
private String weapon;
private int age;
//当加入一个引用数据类型时:
public static Monkey son;
测试结果:
public class ProtoTest {
public static void main(String[] args) {
Monkey monkey=new Monkey("孙悟空","黄色","金箍棒",25);
monkey.son=new Monkey("孙小猴","黄色","小棍",2);
Monkey monkey1 = (Monkey) monkey.clone();
Monkey monkey2 = (Monkey) monkey.clone();
Monkey monkey3 = (Monkey) monkey.clone();
System.out.println(monkey+"引用数据类型的哈希值"+monkey.son.hashCode());
System.out.println(monkey1+"引用数据类型的哈希值"+monkey1.son.hashCode());
System.out.println(monkey2+"引用数据类型的哈希值"+monkey2.son.hashCode());
System.out.println(monkey3+"引用数据类型的哈希值"+monkey3.son.hashCode());
}
}
Monkey{
name='孙悟空', color='黄色', weapon='金箍棒', age=25}引用数据类型的哈希值764977973
Monkey{
name='孙悟空', color='黄色', weapon='金箍棒', age=25}引用数据类型的哈希值764977973
Monkey{
name='孙悟空', color='黄色', weapon='金箍棒', age=25}引用数据类型的哈希值764977973
Monkey{
name='孙悟空', color='黄色', weapon='金箍棒', age=25}引用数据类型的哈希值764977973
Process finished with exit code 0
结论:可以看出,在进行原型模式进行克隆时,对于基本数据类型直接赋值,而对于引用数据类型的数据,进行引用传递(内存地址)。
即浅拷贝默认使用clone()方法来实现的
2.深拷贝
深拷贝是:对于基本数据类型要复制值,对于引用数据类型,要申请存储空间,并复制每一个引用数据类型成员变量所引用的对象,对整个对象进行拷贝
深拷贝的实现方式
a. 重写clone方法来实现深拷贝
package com.njupt.prtoTypeMode;
import java.io.Serializable;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/09/20:48
* @Description:孙悟空的引用数据类型的妻子
*/
public class Fox implements Cloneable, Serializable {
private String name;
private int age;
public Fox() {
}
public Fox(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Fox{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
//默认的拷贝方式是对基本数据类型进行值传递
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
孙悟空的类信息
package com.njupt.prtoTypeMode;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/09/19:44
* @Description:深克隆的演示
*/
public class Monkey implements Cloneable{
private String name;
private String color;
private String weapon;
private int age;
//假设猴子有一个妻子
public Fox wife;
public Monkey son;
public Monkey() {
}
public Monkey(String name, String color, String weapon, int age) {
super();
this.name = name;
this.color = color;
this.weapon = weapon;
this.age = age;
}
@Override
public String toString() {
return "Monkey{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", weapon='" + weapon + '\'' +
", age=" + age +
'}';
}
//原型模式要重写clone方法
@Override
protected Object clone() {
//先对此类中的基本数据类型进行克隆,值传递
Monkey monkey=null;
try {
monkey= (Monkey) super.clone();
//对应用类型的属性进行单独处理
monkey.wife=(Fox) wife.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return monkey;
}
}
测试代码
package com.njupt.prtoTypeMode;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/09/20:52
* @Description:
*/
public class DeepCloneTest {
public static void main(String[] args) {
Monkey monkey = new Monkey("monkey", "yellow", "bang", 25);
monkey.wife=new Fox("狐狸",24);
System.out.println(monkey+"深克隆之前的引用数据类型地址"+monkey.wife.hashCode());
Monkey m2= (Monkey)monkey.clone();
System.out.println(m2+"深克隆之后的引用数据类型地址"+m2.wife.hashCode());
}
}
测试结果:发现引用数据类型wife的地址变了,说明进行了深克隆
Monkey{
name='monkey', color='yellow', weapon='bang', age=25}深克隆之前的引用数据类型地址381259350
Monkey{
name='monkey', color='yellow', weapon='bang', age=25}深克隆之后的引用数据类型地址824318946
Process finished with exit code 0
b.通过对象序列化来实现深拷贝
利用序列化的特点:可以实现引用数据类型的保存。
就可以先将原始类进行序列化,在进行反序列化,就可以实现引用数据类型的深拷贝
//将此方法加在monkey类中。
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);
Monkey monkey = (Monkey) ois.readObject();
return monkey;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试代码
public class DeepCloneTest {
public static void main(String[] args) {
Monkey monkey = new Monkey("monkey", "yellow", "bang", 25);
monkey.wife=new Fox("狐狸",24);
System.out.println(monkey+"深克隆之前的引用数据类型地址"+monkey.wife.hashCode());
/*System.out.println(monkey+"深克隆之前的引用数据类型地址"+monkey.wife.hashCode());
Monkey m2= (Monkey)monkey.clone();
System.out.println(m2+"深克隆之后的引用数据类型地址"+m2.wife.hashCode());*/
//第二种,直接进行序列化进行深克隆
Monkey m2 = (Monkey) monkey.deepClone();
System.out.println(m2+"深克隆之后的引用数据类型地址"+m2.wife.hashCode());
}
}
结果:也实现了深克隆
Monkey{
name='monkey', color='yellow', weapon='bang', age=25}深克隆之前的引用数据类型地址381259350
Monkey{
name='monkey', color='yellow', weapon='bang', age=25}深克隆之后的引用数据类型地址693632176
Process finished with exit code 0
建造者模式
建造者模式又叫做生成器模式,是一种对象构建模式,它可以将复杂的对象的创建过程抽象出来。使这个抽象过程的不同实现方法可以构造出不同属性的对象。
建造者模式的4种角色
- Product(产品角色):一个具体的产品对象
- Builder(抽象创建者):创建一个Product对象的各个部件指定的接口/抽象类
- ConcreatBuilder(具体的创建者):实现抽象接口或者抽象类,负责构建和实现具体的零件功能
- Director(指挥者):构建一个Bulider接口的对象。它主要是用于创建一个复杂的对象,作用有两个,一是:隔离用户与对象的生产对象,二是负责控制产品对象的生产过程
以盖房子为例:
a.产品角色就是房子,房子视为对象,就是有不同的属性
public class House {
private String base;
private String wall;
private String roof;}
b.抽象的创建者就是创建房子的各个步骤,该所有的房子都是要三步:打地基,砌墙,屋顶,变成抽象类,在写上抽象方法供建造不同款式的房子进行重写
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/10/12:03
* @Description:建造者模式
*/
public abstract class BuildModel {
//将与房子视为组合关系。
House house=new House();
abstract void buildBase();
abstract void buildWall();
abstract void buildRoof();
public House build(){
return house;
}
}
c.具体的创建者:针对不同的房子类型,创建出不同的建造房子的方式,继承抽象方法,实现接口即可。
比如要建造一个普通的房子
package com.njupt.simpleFactory.buildModel;
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/10/12:50
* @Description:
*/
public class NomalHouse extends BuildModel {
@Override
void buildBase() {
System.out.println("建造普通方子的地基2米");
}
@Override
void buildWall() {
System.out.println("建造普通房子的墙3米");
}
@Override
void buildRoof() {
System.out.println("建造普通房子的屋顶小型");
}
}
比如要建造一个别墅
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/10/13:46
* @Description:
*/
public class VillaHouse extends BuildModel {
@Override
void buildBase() {
System.out.println("别墅的地基3米");
}
@Override
void buildWall() {
System.out.println("别墅的墙5米");
}
@Override
void buildRoof() {
System.out.println("别墅的房顶大");
}
}
d,.指挥者可以视为包工头,当客户找到包工头时,告诉他建造什么样的方法,他就会根据需求创建出房子
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/10/12:53
* @Description:
*/
public class Boss {
//属性是针对整个抽象方法,这样可以更加灵活,关系是聚合
private BuildModel buildModel;
public Boss(BuildModel buildModel) {
this.buildModel = buildModel;
}
public House creat(){
//建造的具体过程也应该交给包工头去建造
buildModel.buildBase();
buildModel.buildWall();
buildModel.buildRoof();
//建造好之后就可以返回建造之后的结果
return buildModel.build();
}
}
最后,当客户前来建造房子时,只需要找到包工头,告诉他自己想要什么款式的房子,就可以。
/**
* Creat with IntelliJ IDEA
*
* @Auther:倔强的加瓦
* @Date:2021/10/10/12:53
* @Description:
*/
public class Client {
public static void main(String[] args) {
//通过构造器传入要盖多大的房子
Boss boss = new Boss(new NomalHouse());
//创建之后的结果返回给客户
House creat = boss.creat();
//再次建造一个别墅
Boss boss1=new Boss(new VillaHouse());
//创建好之后,将别墅返回给客户
House villa = boss1.creat();
}
}
注意事项:
创建者模式一般适合所创建的产品具有较多的共同点,组成部分相似,如盖房子都需要打地基,砌墙,封顶等步骤,就比较适合建造者模式,入伏哦产品之间的差异性很大,则不适合使用建造者模式
抽象工厂模式和建造者模式的区别:
抽象工厂模式实现对产品家族的创建,一个产品家族就是一系列的产品。具有不同费雷维度的产品组合
采用抽象工厂模式不需要知道构建的过程,只关心产品是由什么工厂生产的,而建造者模式则要求按照指定的蓝图生产产品,主要的目的是:组装零件生产一个新的产品。
适配器模式
作用是将原本不兼容的接口融合在一起工作,可以按照转换成兼容过程中的方式分成三类。
类适配器
使用场景:不能够使用原始类,需要借助一个适配器类来简介的使用,这个原始类叫做:source类,适配器类叫做Adapter类,转化之后的类叫做目标类dst
类适配器模式是指:
Adapter类通过继承src类,实现dst类接口,可以完成src–dst的适配。
类适配器的局限性:因为要使用原始的类,因此要继承原来的src类,但是因为java是单继承机制,所以dst类必须要用接口实现,也就是adapter类要通过继承src类,实现dst类接口,可以完成src–dst的适配。但是继承这一点是缺点,但是正是因为继承了原始的类,因此可以根据要求重写src类的方法,使得Adapter的灵活性增加了。
对象适配器
对象适配器思路和类适配器一样,知识将Adapter类做了修改,不是继承src类,而是将src类聚合到Adapter类中,实现dst接口,也可以完成src-dst的适配
解决了Adapter类适配器必须要继承src类的问题,用关联关系代替了继承关系,使成本更低,并且也不在要求dst必须是一个接口
接口适配器模式
使用场景:当不需要全部实现接口提供的默认方法时,可以使用接口适配器模式,就是先创建一个抽象的类,实现接口,然后对接口中的每一个方法提供一个默认的实现方法,然后抽象类可以有选择的覆盖父类的需要的方法进行重写。
桥接模式
桥接模式是指:将实现抽象放在两个不同的类层次中,是两个层次可以独立的改变结构,是一种结构型设计模式。主要的特点是:把抽象层与实现层分开,从而可以保持各部分的独立性以及应对他们功能的扩展。
桥接模式的原理图
说明:
Client类是桥接模式的调用者
抽象类Abstraction:维护了Implementor(也包括他的实现类)两个是聚合关系,Abstraction是桥接类
Implementor是行为实现类的接口
接口类和抽象类是聚合的关系。
例如用桥接模式解决手机操作的问题:,如果希望添加一种直立样式的手机,按照桥接模式就比较方便
首先将手机的品牌当作一个接口,里面抽象出所有品牌手机都应该有的功能,苹果华为等品牌实现接口
public interface Brand {
public void music();
public void play();
public void video();
}
public class Apple implements Brand {
@Override
public void music() {
System.out.println("苹果手机听音乐");
}
@Override
public void play() {
System.out.println("苹果手机玩游戏");
}
@Override
public void video() {
System.out.println("苹果手机看视频");
}
}
public class Huawei implements Brand {
@Override
public void music() {
System.out.println("华为手机听音乐");
}
@Override
public void play() {
System.out.println("华为手机玩游戏");
}
@Override
public void video() {
System.out.println("华为手机看视频");
}
}
然后将手机类定义为抽象的类,里面聚合手机的品牌接口Brand,并且通过构造方法来进行传值,这样就可以针对不同的手机品牌实例化不同的品牌
public abstract class Phone {
Brand brand;
public Phone(Brand brand){
this.brand=brand;
}
public void play(){
brand.play();
}
public void video(){
brand.video();
}
public void music(){
brand.music();
}
}
不同样式的手机继承抽象的类,重写相应手机在实现功能时自己的具体功能
//折叠手机的具体功能
public class FoldPhone extends Phone {
public FoldPhone(Brand brand) {
super(brand);
}
public void play(){
System.out.println("折叠的");
brand.play();
}
public void music(){
System.out.println("折叠的");
brand.music();
}
public void video(){
System.out.println("折叠的");
brand.video();
}
}
触屏手机的具体功能
public class Touch extends Phone {
public Touch(Brand brand) {
super(brand);
}
public void play(){
System.out.println("触屏的");
brand.play();
}
public void music(){
System.out.println("触屏的");
brand.music();
}
public void video(){
System.out.println("触屏的");
brand.video();
}
}
测试类顾客:
public class Client {
public static void main(String[] args) {
FoldPhone foldPhone = new FoldPhone(new Huawei());
foldPhone.play();
foldPhone.music();
foldPhone.video();
Touch touch = new Touch(new Huawei());
touch.music();
touch.play();
touch.video();
}
}
结果:
折叠的
华为手机玩游戏
折叠的
华为手机听音乐
折叠的
华为手机看视频
触屏的
华为手机听音乐
触屏的
华为手机玩游戏
触屏的
华为手机看视频
Process finished with exit code 0
当需要新添加直立样式的手机时,只需要重新继承抽象的手机类,然后写直立手机应该特有的功能即可。
装饰者模式
装饰者模式:动态的将新功能附加带对象上,在对象的功能扩展方面,他比继承更有弹性,体现了开闭原则
享元模式(FlyWeight Pattern)
共享对象
在java中,此模式可以解决重复对象的内存浪费问题,像需求相似度很高时,可以考虑使用享元模式来解决,如String字符串和线程池的创建都用到了享元模式
图解说明:
- FlyWeight:是抽象的享元角色,他是产品的抽象类,同时定义出对象的外部状态(容易改变的状态,对象之间不可共享的信息)和内部状态(不轻易改变的状态,对象之间可共享的信息)的接口或实现类。
- ConcreteFlyWeight是具体的享元角色,是具体的产品类,实现抽象角色相关业务
- UnsharedConcreteFlyweight是不可共享的角色,一般不会出现在享元工厂中
- Factory是享元工厂类,用于构建一个池容器,同时还要提供从池中获取对象的方法。
代码展示
//外部状态,针对不同的使用者来使用网站
public class User {
private String name;
public User(String name){
this.name=name;
}
}
//共享的抽象类
public abstract class FlyWeight {
public abstract void use(User user);
}
//具体的实现类
public class ConcreteFlyWeight extends FlyWeight {
private String type="";
public ConcreteFlyWeight(String type){
this.type=type;
}
@Override
public void use(User user) {
System.out.println(user+"当前正在使用"+type);
}
}
//工厂类
public class flyWeightFactory {
private static HashMap<String,FlyWeight> pool=new HashMap<>();
public static FlyWeight getConcrete(String type){
if(!pool.containsKey(type)){
ConcreteFlyWeight weight = new ConcreteFlyWeight(type);
pool.put(type,weight);
}
return pool.get(type);
}
public static int poolSize(){
return pool.size();
}
}
//测试类
public class WebTest {
public static void main(String[] args) {
FlyWeight concrete = flyWeightFactory.getConcrete("知乎");
concrete.use(new User("小明"));
FlyWeight weight = flyWeightFactory.getConcrete("斗鱼");
weight.use(new User("zhangsan"));
FlyWeight weight1 = flyWeightFactory.getConcrete("斗鱼");
weight1.use(new User("历史"));
FlyWeight weight2 = flyWeightFactory.getConcrete("斗鱼");
weight2.use(new User("王维护"));
System.out.println("线程池中的对象数:"+flyWeightFactory.poolSize());
}
}
测试结果:可以看出虽然使用了4个对象,但是最后池中只有2个对象
User{
name='小明'}当前正在使用知乎
User{
name='zhangsan'}当前正在使用斗鱼
User{
name='历史'}当前正在使用斗鱼
User{
name='王维护'}当前正在使用斗鱼
线程池中的对象数:2
Process finished with exit code 0