狂神说Java设计模式

1. 什么是设计模式?

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,是一套被反复使用、多数人知晓、及经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保障代码的可靠性,可维护性。

2. 面向对象设计(OOP)七大原则

  • 开闭原则:对扩展开放、对修改关闭
  • 里式替换原则:继承需要修改父类的性质
  • 依赖倒置原则:要面向接口编程、不要面向实现编程
  • 针对接口编程的意思就是说,应当使用Java接口和抽象Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。
  • 不要针对实现编程的意思就是说,不应当使用具体Java类进行变量的类型声明、参量的类型声明、方法的返回类型声明,以及数据类型的转换等。
  • 单一职责原则:控制类的粒度大小,一个类只负责一个职责
  • 接口隔离原则:各个类建立它们需要的专用接口

通俗一点来讲就是接口应该尽量细化,一个接口对应一个功能模块的规范,同时让里面的方法尽可能少,使接口轻便灵活

  • 迪米特原则:一个对象应该对其他对象有最少的了解

通俗一点来讲就是一个类对自己需要耦合或者调用的类知道的最少,你(被耦合或者调用的类)的内部是如何复杂和我没有关系,我就知道你提供的public方法,其他我不关心。

  • 合成复用原则:尽量先使用组合或者聚合等关系来实现,其次才考虑使用继承关系来实现。

3. GoF23(23中设计模式)有哪些?

  • 创建型模式
    1. 单例模式
    2. 工厂模式
    3. 抽象工厂模式
    4. 建造者模式
    5. 原型模式
  • 结构型模式
    1. 适配器模式
    2. 桥接模式
    3. 装饰模式
    4. 组合模式
    5. 外观模式
    6. 享元模式
    7. 代理模式
  • 行为型模式
    1. 模板方法模式
    2. 命令模式
    3. 迭代器模式
    4. 观察者模式
    5. 中介者模式
    6. 备忘录模式
    7. 解释器模式
    8. 状态模式
    9. 策略模式
    10. 职责模式
    11. 访问者模式

4. 创建型–工厂模式

工厂模式:创建者和调用者分离
4.1 简单工厂模式
定义:定义一个生成对象的工厂类,客户端通过工厂类的静态方法来获取对象。
举例:
在这里插入图片描述

说明:我们定义一个生产汽车的工厂,客户端通过调用工厂类的静态方法并传入所需汽车的名字来获取对象。
缺点:如果我们需要增加其他的车型,那么便需要去工厂类去改变其原则代码,显然这违背了开闭原则 扩展性低。

4.2 工厂方法模式
定义:定义一个生产对象的接口类,由子类实现接口来决定要实例化的类。工厂方法模式将生产的对象推迟到子类去实现。

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

  • 简单工厂模式是生产单一的产品
  • 工厂方法模式是声场一个系列的产品族

接口工厂类
在这里插入图片描述
具体的工厂类
在这里插入图片描述
测试类
在这里插入图片描述
说明:根据不同的车型去实现不同的工厂类,通过其对应的工厂类来获取相应的车型。
优点:相对简单工厂模式,它支持横向扩展,无需改变源代码
缺点:相对简单工厂模式,它代码冗余读高,难以维护。

5. 创建型-- 抽象工厂模式

它是创建工厂的工厂,可以称为超级工厂
定义:定义了一个接口用于创建相关或由依赖关系的对象族,而无需明确指定具体类。比如说小米产品系列,其旗下产品有小米手机,小米充电器,小米路由器等等。

抽象工厂模式与工厂方法模式很类似

  • 工厂方法模式:小米公司只是卖手机
  • 抽象工厂模式:小米公司卖一系列产品:手机,路由器,电脑等等
    在这里插入图片描述
    抽象产品工厂
    在这里插入图片描述
    具体产品工厂
    在这里插入图片描述
    在这里插入图片描述
    说明:有华为品牌的手机和路由器,也有小米品牌的手机和路由器。我个人理解抽象工厂模式与工厂方法模式没有什么区别,抽象工厂模式只是在工厂方法模式上添加了其他的方法,通俗点来讲就是小米不仅卖手机,还卖路由器。
    缺点:抽象工厂模式与简单工厂模式有着一样的局限性,就是扩展性低,如果需要添加其他的产品,就需要更改工厂代码。

5.创建型–建造者模式

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。用户只需给出指定类型的复杂对象和内容,指挥者指挥建造者按照指定的顺序创建复杂的对象。

建造者模式与抽象工厂的比较

  • 个人理解建造者模式如下:建造者模式与工厂方法模式有点类似,只不过工厂方法的工厂类是直接生产出完成的产品,而建造者模式下该工厂(工人)则是生产出产品的一个个组件,然后需要一个指挥者指挥工人按照指定的顺序去拼接出一个产品。

建造者模式与抽象工厂的比较

  • 建造者模式返回一个组装完整的产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构构成了产品族。
  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法来获取所需的产品对象,而在建造者模式中,客户端可以通过指挥者类来指导如何生产对象,也可以调用建造者的相关方法来指导生产对象,它侧重一步步构建一个复杂的对象返回一个完整的产品对象。
    如果将抽象工厂模式看做汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一个完整的汽车

产品类-一个产品的所有属性

在这里插入图片描述

抽象建造者类-定义建造者需要做的组件
在这里插入图片描述
具体建造者类-创建出产品的所有组件
在这里插入图片描述

指挥者-指挥工人生成房子
在这里插入图片描述
说明:具体的工厂(工人)去生产产品的一个个零件 ,由指挥者去指挥工人按照指定的顺序去拼接。
优点

  • 产品的建造和表示分离,实现解耦
  • 具体的创建者之间相互独立,利于系统的扩展,符合“开闭原则”

缺点

  • 建造者模式所创建的产品一般具有较多的共同点,如果产品之间的差异性比较大,那么不适合哟呵你建造者模式
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体的建造者来实现这种变化,导致系统变得很庞大。

6. 创建型–原型模式

定义:以一个对象为模板,创建出一个一模一样的深拷贝对象

实现方式:

  1. 实现Cloneable接口。
  2. 重写clone方法实现深拷贝

在这里插入图片描述

6.5. 创建型–单例模式

草帽小子–面试常问设计模式——单例模式

7. 结构型–适配器模式

定义:适配器模式是将某一个接口转换成客户端期待的另一个接口,目的是消除由于接口不匹配所造成的的类的不兼容性问题。

主要有三类实现方式:类的适配器模式、对象的适配器模式、接口的适配器模式。

使用方式
客户端期待的接口XXX,定义一个适配器类去实现接口XXX,并在适配器类中可通过继承被适配类或者创建被适配类对象的方式获取目标API方法,然后在适配器类的方法中调用目标函数,而客户端直接调用适配器的方法便可实现转换

在这里插入图片描述

7.1 类适配器模式
通过多重继承目标接口和被适配者类方式实现适配
举例:将USB接口转换为VGA接口
在这里插入图片描述
USBImpl的代码:

public class USBImpl implements USB{
       @Override
       public void showPPT() {
              // TODO Auto-generated method stub
              System.out.println("PPT内容演示");
       }
}

AdatperUSB2VGA 首先继承USBImpl获取可连接USB的功能,其次,实现VGA接口,表示该类的类型为VGA。

public class AdapterUSB2VGA extends USBImpl implements VGA {
       @Override
       public void projection() {
              super.showPPT();
       }
}

Projector将USB映射为VGA,只有VGA接口才可以连接上投影仪进行投影

public class Projector<T> {
       public void projection(T t) {
              if (t instanceof VGA) {
                     System.out.println("开始投影");
                     VGA v = new VGAImpl();
                     v = (VGA) t;
                     v.projection();
              } else {
                     System.out.println("接口不匹配,无法投影");
              }
       }
}

test代码

       @Test
       public void test2(){
              //通过适配器创建一个VGA对象,这个适配器实际是使用的是USB的showPPT()方法
              VGA a=new AdapterUSB2VGA();
              //进行投影
              Projector p1=new Projector();
              p1.projection(a);
       } 

7.2 对象适配器模式
对象适配器和类适配器使用了不同的方法实现适配,对象适配器使用组合,类适配器使用继承。

举例(将USB接口转为VGA接口),类图如下:
在这里插入图片描述
实现VGA接口是为了满足客户端的需求,客户端通过VGA类型对象调用适配器方法。
创建USB对象是为了在适配器中使用USB的功能。

public class AdapterUSB2VGA implements VGA {
       USB u = new USBImpl();
       @Override
       public void projection() {
              u.showPPT();
       }
}

实现VGA接口,表示适配器类是VGA类型的,适配器方法中直接使用USB对象。

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

举例(将USB接口转为VGA接口,VGA中的b()和c()不会被实现),类图如下:
在这里插入图片描述
AdapterUSB2VGA抽象类

public abstract class AdapterUSB2VGA implements VGA {
       USB u = new USBImpl();
       @Override
       public void projection() {
              u.showPPT();
       }
       @Override
       public void b() {
       };
       @Override
       public void c() {
       };
}

AdapterUSB2VGA实现,不用去实现b()和c()方法。

public class AdapterUSB2VGAImpl extends AdapterUSB2VGA {
       public void projection() {
              super.projection();
       }
}

总结
在这里插入图片描述
使用选择:
根据合成复用原则,组合大于继承。因此,类的适配器模式应该少用。

8. 结构型–桥接模式

定义: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。

桥接模式与适配器模式的比较 共同点:桥接和适配器都是让两个东西配合工作。 不同点:

  1. 适配器:改变已有的两个接口,让它们相容
  2. 桥接模式:分离抽象化和实现,目的是分离,适合维度变化

8.1 案例
看下图手机与手机软件的类图
在这里插入图片描述
增加一款新的手机软件,需要在所有手机品牌类下添加对应的手机软件类,当手机软件种类较多时,将导致类的个数急剧膨胀,难以维护。

手机和手机中的软件是什么关系?
手机中的软件从本质上来说并不是一种手机,手机软件运行在手机中,是一种包含与被包含关系,而不是一种父与子或者说一般与特殊的关系,通过继承手机类实现手机软件类的设计是违反一般规律的。

换一种解决思路
在这里插入图片描述
从类图上看起来更像是手机软件类图,涉及到手机本身相关的功能,比如说:wifi功能,放到哪个类中实现呢?放到OppoAppStore中实现显然是不合适的。

引起整个结构变化的元素有两个,一个是手机品牌,一个是手机软件,所以我们将这两个点抽出来,分别进行封装。
在这里插入图片描述
8.2 桥接模式结构和代码示例
在这里插入图片描述
在这里插入图片描述
实现:

public interface Software {
	public void run();
 
}
public class AppStore implements Software {
	 
    @Override
    public void run() {
        System.out.println("run app store");
    }
}
public class Camera implements Software {
	 
    @Override
    public void run() {
        System.out.println("run camera");
    }
}

抽象:

public abstract class Phone {
 
	protected Software software;
 
	public void setSoftware(Software software) {
		this.software = software;
	}
 
	public abstract void run();
 
}
public class Oppo extends Phone {
	 
    @Override
    public void run() {
        software.run();
    }
}
public class Vivo extends Phone {
	 
    @Override
    public void run() {
        software.run();
    }
}

对比最初的设计,将抽象部分(手机)与它的实现部分(手机软件类)分离,将实现部分抽象成单独的类,使它们都可以独立地变化。整个类图看起来像一座桥,所以称为桥接模式

继承是一种强耦合关系,子类的实现与它的父类有非常紧密的依赖关系,父类的任何变化 都会导致子类发生变化,因此继承或者说强耦合关系严重影响了类的灵活性,并最终限制了可复用性

从桥接模式的设计上我们可以看出聚合是一种比继承要弱的关联关系,手机类和软件类都可独立的进行变化,不会互相影响

9.3 适用场景
桥接模式通常适用于以下场景。

  • 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
  • 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
  • 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。

8.4 优缺点
优点:

  • 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数。
  • 桥接模式提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。

缺点:

  • 桥接模式的使用会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。

9. 结构型–装饰者模式

定义:动态的奖新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性。

星巴克咖啡订单项目(咖啡馆):

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

10. 结构型–组合模式

定义: 有时又叫作部分-整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示“部分-整体”的关系,使用户对单个对象和组合对象具有一致的访问性。

意图: 将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

何时使用:
1、您想表示对象的部分-整体层次结构(树形结构)。
2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
组合模式的主要优点:

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  • 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  • 不容易限制容器中的构件;
  • 不容易用继承的方法来增加构件的新功能;

10.1 模式结构和代码示例
在这里插入图片描述
抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。

树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中 声明的公共接口。

树枝构件(Composite)角色:是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法

举例(访问一颗树),类图如下:
在这里插入图片描述
1 组件

public interface Component {

    public void add(Component c);
    
    public void remove(Component c);
    
    public Component getChild(int i);
    
    public void operation();
 
}

2 树枝

public class Composite implements Component {
 
	private ArrayList<Component> children = new ArrayList<Component>();
 
	public void add(Component c) {
		children.add(c);
	}
 
	public void remove(Component c) {
		children.remove(c);
	}
 
	public Component getChild(int i) {
		return children.get(i);
	}
 
	public void operation() {
		for (Object obj : children) {
			((Component) obj).operation();
		}
	}
}

3 叶子

public class Leaf implements Component{
    
	private String name;
	
	public Leaf(String name) {
		this.name = name;
	}
 
	@Override
	public void add(Component c) {}
 
	@Override
	public void remove(Component c) {}
 
	@Override
	public Component getChild(int i) {
		// TODO Auto-generated method stub
		return null;
	}
 
	@Override
	public void operation() {
		// TODO Auto-generated method stub
		 System.out.println("树叶"+name+":被访问!"); 
	}
 
}

11. 结构型–代理模式

11.1 静态代理
静态代理在程序运行之处就已经写死,无法修改代理类

静态代理角色分析

  • 抽象角色 : 一般使用接口或者抽象类来实现
  • 真实角色 : 被代理的角色
  • 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
  • 客户 : 使用代理角色来进行一些操作 .

上代码
Rent.java 即抽象角色:要出租房子

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host.java 即真实角色-房东要出租房子

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy.java 即代理角色-中介帮助房东租出房子

//代理角色:中介
public class Proxy implements Rent {

   private Host host;
   public Proxy() { }
   public Proxy(Host host) {
       this.host = host;
  }

   //租房
   public void rent(){
       seeHouse();
       host.rent();
       fare();
  }
   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }
}

Client.java 即客户-客户向中介租房子

//客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       //房东要租房
       Host host = new Host();
       //中介帮助房东
       Proxy proxy = new Proxy(host);

       //你去找中介!
       proxy.rent();
  }
}

静态代理的好处

  • 可以使得我们的真实角色业务更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 专业的业务由真实角色完成,实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .

静态代理的缺点 :

  • 由于多了代理类,因此类变得更多了 , 导致工作量变大. 开发效率降低

11.2 动态代理
没有向静态代理一样一开始就直接写死代理的对象,而是根据具体的真实角色去代理。

静态代理和动态代理的比较

  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的

动态代理的方式

  • 基于接口的动态代理----JDK动态代理
  • 基于类的动态代理–cglib
  • 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist

基于接口的JDK动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看

【InvocationHandler:调用处理程序】

Object invoke(Object proxy, 方法 method, Object[] args)//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。

【Proxy : 代理】
在这里插入图片描述
在这里插入图片描述

【Proxy : 代理】

//生成代理类
public Object getProxy(){
   return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                 rent.getClass().getInterfaces(),this);
}

上代码

抽象角色和真实角色和之前的一样!

Rent.java 即抽象角色

//抽象角色:租房
public interface Rent {
   public void rent();
}

Host.java 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

ProxyInvocationHandler.java 即代理角色

public class ProxyInvocationHandler implements InvocationHandler {
   private Object target;

   public void setTarget(Object target) {
       this.target = target;
  }

   //生成代理类
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               target.getClass().getInterfaces(),this);
  }

   // proxy : 代理类
   // method : 代理类的调用处理程序的方法对象.
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       log(method.getName());
       Object result = method.invoke(target, args);
       return result;
  }

   public void log(String methodName){
       System.out.println("执行了"+methodName+"方法");
  }

}

Client.java

public class Test {
   public static void main(String[] args) {
       //真实对象
       UserServiceImpl userService = new UserServiceImpl();
       //代理对象的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setTarget(userService); //设置要代理的对象
       UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
       proxy.delete();
  }
}

动态代理的好处

  • 静态代理有的它都有,静态代理没有的,它也有!
  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值