Java常见设计模式

Posted Firm陈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java常见设计模式相关的知识,希望对你有一定的参考价值。

一.设计模式概述

设计模式是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 设计模式于己于人于系统都是多赢的,它使得代码编写真正工程化,它是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。总体来说,设计模式分为三大类:

创建型模式(创建对象,共5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式(类与类之间的结构,共7种):适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式
行为型模式(方法与方法之间的组合使用,共11种):策略模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式

其实还有两类:并发型模式和线程池模式,用一个图片来整体描述一下:

二.设计模式的六大原则

1.开闭原则 (Open Close Principle)
对扩展代码开放,对修改代码关闭。使用接口或抽象类

2.依赖倒转原则 (Dependence Inversion Principle)
针对接口编程,依赖于抽象而不依赖于具体(定义成员变量时,使用抽象类型)

3.里氏替换原则 (Liskov Substitution Principle)
里氏替换原则是继承与复用的基石,只有当子类可以替换掉基类,且系统的功能不受影响时,基类才能被复用,而子类也能够在基类上增加新的行为。所以里氏替换原则指的是任何基类可以出现的地方,子类一定可以出现。(基类完成的功能,可以使用子类来代替的)

4.接口隔离原则 (Interface Segregation Principle)
使用多个隔离的接口,比使用单个接口要好,降低接口之间的耦合度与依赖,方便升级和维护方便

5.迪米特原则 (Demeter Principle)
迪米特原则,也叫最少知道原则,指的是一个类应当尽量减少与其他实体进行相互作用,使得系统功能模块相对独立,降低耦合关系。该原则的初衷是降低类的耦合,虽然可以避免与非直接的类通信,但是要通信,就必然会通过一个“中介”来发生关系,过分的使用迪米特原则,会产生大量的中介和传递类,导致系统复杂度变大,所以采用迪米特法则时要反复权衡,既要做到结构清晰,又要高内聚低耦合。
6.合成复用原则 (Composite Reuse Principle)
尽量使用组合/聚合的方式,而不是使用继承。

三.具体的设计模式介绍

第一类:创建型模式
1.简单工厂模式
概述:简单工厂模式的核心是定义一个创建对象的接口,将对象的创建和本身的业务逻辑分离,降低系统的耦合度,使得两个修改起来相对容易些,当以后实现改变时,只需要修改工厂类即可。

如果不使用工厂,用户将自己创建宝马车,具体UML图和代码如下:

public class BMW320 
	public BMW320()
		System.out.println("制造-->BMW320");
	

 
public class BMW523 
	public BMW523()
		System.out.println("制造-->BMW523");
	

 
public class Customer 
	public static void main(String[] args) 
		BMW320 bmw320 = new BMW320();
		BMW523 bmw523 = new BMW523();
	

用户需要知道怎么创建一款车,这样子客户和车就紧密耦合在一起了,为了降低耦合,就出现了简单工厂模式,把创建宝马的操作细节都放到了工厂里,而客户直接使用工厂的创建方法,传入想要的宝马车型号就行了,而不必去知道创建的细节。
简单工厂模式的UML图:

工厂类角色: 该模式的核心,用来创建产品,含有一定的商业逻辑和判断逻辑
抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。

产品类:

abstract class BMW 
	public BMW()

 
public class BMW320 extends BMW 
	public BMW320() 
		System.out.println("制造-->BMW320");
	

public class BMW523 extends BMW
	public BMW523()
		System.out.println("制造-->BMW523");
	

工厂类:

public class Factory 
	public BMW createBMW(int type) 
		switch (type) 
		
		case 320:
			return new BMW320();
 
		case 523:
			return new BMW523();
 
		default:
			break;
		
		return null;
	

用户类:

public class Customer 
	public static void main(String[] args) 
		Factory factory = new Factory();
		BMW bmw320 = factory.createBMW(320);
		BMW bmw523 = factory.createBMW(523);
	

简单工厂模式的优缺点:
简单工厂模式提供专门的工厂类用于创建对象,实现了对象创建和使用的职责分离,客户端不需知道所创建的具体产品类的类名以及创建过程,只需知道具体产品类所对应的参数即可,通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

但缺点在于不符合“开闭原则”,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。

为了解决简单工厂模式的问题,出现了工厂方法模式。

总结:简单工厂模式就是抽象产品类,创建工厂类,让工厂帮你创建对象。根据不同的传参创建不同的对象。这样用户就可以通过创建工厂类对象调用方法传入不同的参数获取不同的产品对象。

2.工厂方法模式
工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。在使用时,用于只需知道产品对应的具体工厂,关注具体的创建过程,甚至不需要知道具体产品类的类名,当我们选择哪个具体工厂时,就已经决定了实际创建的产品是哪个了。

但缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

工厂方法的 UML 结构图如下:

抽象工厂 AbstractFactory: 工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类,在 Java 中它由抽象类或者接口来实现。
具体工厂 Factory:被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码
抽象产品 AbstractProduct:是具体产品继承的父类或实现的接口,在 Java 中一般有抽象类或者接口来实现。
具体产品 Product:具体工厂角色所创建的对象就是此角色的实例。

产品类:

abstract class BMW 
	public BMW()

public class BMW320 extends BMW 
	public BMW320() 
		System.out.println("制造-->BMW320");
	

public class BMW523 extends BMW
	public BMW523()
		System.out.println("制造-->BMW523");
	

工厂类:

interface FactoryBMW 
	BMW createBMW();

 
public class FactoryBMW320 implements FactoryBMW
 
	@Override
	public BMW320 createBMW() 
		return new BMW320();
	
 

public class FactoryBMW523 implements FactoryBMW 
	@Override
	public BMW523 createBMW() 
		return new BMW523();
	

客户类:

public class Customer 
	public static void main(String[] args) 
		FactoryBMW320 factoryBMW320 = new FactoryBMW320();
		BMW320 bmw320 = factoryBMW320.createBMW();
 
		FactoryBMW523 factoryBMW523 = new FactoryBMW523();
		BMW523 bmw523 = factoryBMW523.createBMW();
	

总结:工厂方法模式就是抽象工厂类,抽象产品类,让不同的工厂生产不同的产品(一个工厂生产一个对应的产品)

3.抽象工厂模式
在工厂方法模式中,我们使用一个工厂创建一个产品,一个具体工厂对应一个具体产品,但有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
在介绍抽象工厂模式前,我们先厘清两个概念:

  • 产品等级结构:产品等级结构指的是产品的继承结构,例如一个空调抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个空调抽象类和他的子类就构成了一个产品等级结构。
  • 产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产海尔空调、海尔冰箱,那么海尔空调则位于空调产品族中。
    产品等级结构和产品族结构示意图如下:
    横着分是产品等级结构,竖着分是产品族

什么是抽象工厂模式:
抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象;并且通过隔离具体类的生成,使得客户端不需要明确指定具体生成类;所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
但该模式的缺点在于添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。

UML结构图:

抽象工厂 AbstractFactory:定义了一个接口,这个接口包含了一组方法用来生产产品,所有的具体工厂都必须实现此接口。
具体工厂 ConcreteFactory:用于生产不同产品族,要创建一个产品,用户只需使用其中一个工厂进行获取,完全不需要实例化任何产品对象。
抽象产品 AbstractProduct:这是一个产品家族,每一个具体工厂都能够生产一整组产品。
具体产品 Product

通过抽象工厂模式,我们可以实现以下的效果:比如宝马320系列使用空调型号A和发动机型号A,而宝马230系列使用空调型号B和发动机型号B,在为320系列生产相关配件时,就无需制定配件的型号,它会自动根据车型生产对应的配件型号A。
也就是说,当每个抽象产品都有多于一个的具体子类的时候(空调有型号A和B两种,发动机也有型号A和B两种),工厂角色怎么知道实例化哪一个子类呢?抽象工厂模式提供两个具体工厂角色(宝马320系列工厂和宝马230系列工厂),分别对应于这两个具体产品角色,每一个具体工厂角色只负责某一个产品角色的实例化,每一个具体工厂类只负责创建抽象产品的某一个具体子类的实例。

产品类:

//发动机以及型号  
public interface Engine   
 
public class EngineA implements Engine  
    public EngineA()  
        System.out.println("制造-->EngineA");  
      
  
public class EngineB implements Engine  
    public EngineB()  
        System.out.println("制造-->EngineB");  
      
  
 
 
//空调以及型号  
public interface Aircondition  
 
public class AirconditionA implements Aircondition  
    public AirconditionA()  
        System.out.println("制造-->AirconditionA");  
      
  
public class AirconditionB implements Aircondition  
    public AirconditionB()  
        System.out.println("制造-->AirconditionB");  
      
 

创建工厂类:

//创建工厂的接口  
public interface AbstractFactory   
    //制造发动机
    public Engine createEngine();
    //制造空调 
    public Aircondition createAircondition(); 
  
 
//为宝马320系列生产配件  
public class FactoryBMW320 implements AbstractFactory     
    @Override  
    public Engine createEngine()     
        return new EngineA();  
      
    @Override  
    public Aircondition createAircondition()   
        return new AirconditionA();  
      
  
//宝马523系列
public class FactoryBMW523 implements AbstractFactory   
     @Override  
    public Engine createEngine()     
        return new EngineB();  
      
    @Override  
    public Aircondition createAircondition()   
        return new AirconditionB();  
      
 

客户:

public class Customer   
    public static void main(String[] args)  
        //生产宝马320系列配件
        FactoryBMW320 factoryBMW320 = new FactoryBMW320();  
        factoryBMW320.createEngine();
        factoryBMW320.createAircondition();
          
        //生产宝马523系列配件  
        FactoryBMW523 factoryBMW523 = new FactoryBMW523();  
        factoryBMW523.createEngine();
        factoryBMW523.createAircondition();
      

原文链接:https://blog.csdn.net/a745233700/article/details/120253639

总结:抽象工厂模式需要抽象多个产品类,抽象一个工厂类。每个具体的工厂生产一个家族的产品
例如A工厂生产A型号的发动机和空调。B工厂生产B型号的发动机和空调

工厂方法模式与抽象工厂模式的区别在于:

(1)工厂方法只有一个抽象产品类和一个抽象工厂类,但可以派生出多个具体产品类和具体工厂类,每个具体工厂类只能创建一个具体产品类的实例。

(2)抽象工厂模式拥有多个抽象产品类(产品族)和一个抽象工厂类,每个抽象产品类可以派生出多个具体产品类;抽象工厂类也可以派生出多个具体工厂类,同时每个具体工厂类可以创建多个具体产品类的实例

4.建造者模式
建造者模式将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,从而更精确地控制复杂对象的产生过程;通过隔离复杂对象的构建与使用,也就是将产品的创建与产品本身分离开来,使得同样的构建过程可以创建不同的对象;并且每个具体建造者都相互独立,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。

但建造者模式的缺陷是要求创建的产品具有较多的共同点、组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式。同时如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大

UML结构图:

抽象建造者 Builder:相当于建筑蓝图,声明了创建 Product 对象的各个部件指定的抽象接口。
具体建造者 ConcreteBuilder:实现Builder抽象接口,构建和装配各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。
指挥者 Director:构建一个使用 Builder 接口的对象。主要有两个作用,一是隔离用户与对象的生产过程,二是负责控制产品对象的生产过程。
产品角色 Product:被构造的复杂对象。ConcreteBuilder 创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

代码实现:
KFC里面一般都有好几种可供客户选择的套餐,它可以根据客户所点的套餐,然后在后面做这些套餐,返回给客户的是一个完整的、美好的套餐。下面我们将会模拟这个过程,我们约定套餐主要包含汉堡、薯条、可乐、鸡腿等等组成部分,使用不同的组成部分就可以构建出不同的套餐。

套餐类:

public class Meal 
    private String food;
    private String drink;
 
    public String getFood() 
        return food;
    
 
    public void setFood(String food) 
        this.food = food;
    
 
    public String getDrink() 
        return drink;
    
 
    public void setDrink(String drink) 
        this.drink = drink;
    

套餐构造器

public abstract class MealBuilder 
    Meal meal = new Meal();
    
    public abstract void buildFood();
    
    public abstract void buildDrink();
    
    public Meal getMeal()
        return meal;
    

套餐A、套餐B。这两个套餐类都实现抽象套餐类

public class MealA extends MealBuilder
 
    public void buildDrink() 
        meal.setDrink("一杯可乐");
    
 
    public void buildFood() 
        meal.setFood("一盒薯条");
    
 

public class MealB extends MealBuilder
 
    public void buildDrink() 
        meal.setDrink("一杯柠檬果汁");
    
 
    public void buildFood() 
        meal.setFood("三个鸡翅");
    
 

最后是KFC的服务员,它相当于一个指挥者,它决定了套餐是的实现过程,然后给你一个完美的套餐。

public class KFCWaiter 
    private MealBuilder mealBuilder;
    
    public void setMealBuilder(MealBuilder mealBuilder) 
        this.mealBuilder = mealBuilder;
    
 
    public Meal construct()
        //准备食物
        mealBuilder.buildFood();
        //准备饮料
        mealBuilder.buildDrink();
        
        //准备完毕,返回一个完整的套餐给客户
        return mealBuilder.getMeal();
    

测试类:

public class Client 
    public static void main(String[] args) 
        //服务员
        KFCWaiter waiter = new KFCWaiter();
        //套餐A
        MealA a = new MealA();
        //服务员准备套餐A
        waiter.setMealBuilder(a);
        //获得套餐
        Meal mealA = waiter.construct();
        
        System.out.print("套餐A的组成部分:");
        System.out.println(mealA.getFood()+"---"+mealA.getDrink());
    

总结:创建空对象。根据不同的套餐构建不同的对象。至少有A套组合,B套组合。

5.原型模式
原型模式主要用于对象的创建,使用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

UML类图如下:

原型模式的核心是就是原型类 Prototype,Prototype 类需要具备以下两个条件:

(1)实现 Cloneable 接口:在 Java 中 Cloneable 接口的作用就是在运行时通知虚拟机可以安全地在实现了 Cloneable 接口的类上使用 clone() 方法,只有实现了 Cloneable 的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException 异常。

(2)重写 Object 类中的 clone() 方法:Java 中所有类的父类都是 Object,Object 中有一个clone() 方法用于返回对象的拷贝,但是其作用域 protected,一般的类无法调用,因此,Prototype 类需要将 clone() 方法的作用域修改为 public。

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

略---->后续补充

第二类:结构型模式
上面我们介绍了5种创建型模式,下面我们就开始介绍下7种结构型模式:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。其中对象的适配器模式是各种模式的起源,如下图:

6.适配器模式
适配器模式主要用于将一个类的接口转化成客户端希望的目标类格式,使得原本不兼容的类可以在一起工作,将目标类和适配者类解耦;同时也符合“开闭原则”,可以在不修改原代码的基础上增加新的适配器类;将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性,但是缺点在于更换适配器的实现过程比较复杂。
所以,适配器模式比较适合以下场景:
(1)系统需要使用现有的类,而这些类的接口不符合系统的接口。
(2)使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
下面两个非常形象的例子很好地说明了什么是适配器模式:


适配器模式主要分成三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
(1)类的适配器模式

目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
需要适配的类(Adaptee):需要适配的类或适配者类。
适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口。

// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类
class Adaptee 
	public void specificRequest() 
		System.out.println("被适配类具有特殊功能...");
	

 
// 目标接口,或称为标准接口
interface Target 
	public void request();

 
// 具体目标类,只提供普通功能
class ConcreteTarget implements Target 
	public void request() 
		System.out.println("普通类具有普通功能...");
	

 
// 适配器类,继承了被适配类,同时实现标准接口
class Adapter extends Adaptee implements Target
	public void request() 
		super.specificRequest();
	

 
// 测试类public class Client 
	public static void main(String[] args) 
		// 使用普通功能类
		Target concreteTarget = new ConcreteTarget();
		concreteTarget.request();
		
		// 使用特殊功能类,即适配类
		Target adapter = new Adapter();
		adapter.request();
	

核心:实现同样的接口,继承特殊的类

(2)对象的适配器模式

// 适配器类,直接关联被适配类,同时实现标准接口
class Adapter implements Target
	// 直接关联被适配类
	private Adaptee adaptee;
	
	// 可以通过构造函数传入具体需要适配

以上是关于Java常见设计模式的主要内容,如果未能解决你的问题,请参考以下文章

抽象工厂模式

工厂模式

抽象 工厂模式

设计模式-简单工厂工厂方法模式抽象工厂模式详解

抽象工厂模式

抽象工厂模式