设计模式进阶
面向对象的四个基本原则:封装,抽象,继承,多条
设计原则
面向对象的设计原则被称为SOLID,包括
单一职责原则:类的设计应当只负责本类负责的部分
开闭原则:代码设计对修改关闭,对扩展开放
里氏替换原则:派生类应该像其父类一样表现
接口隔离原则:客户端不应该依赖其不需要的接口
依赖倒置原则:高级模块与低级模块都依赖于抽象,细节依赖于抽象
创建型模式(5种)
单例模式
提前加载(饿汉式)
延迟加载(懒汉式)
提前加载
提前加载的单例模式直接在static属性new对象即可,由于static代码段在类创建时就会创建对象,因此保证了只会被创建一次
public class Singleton {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
System.out.println("单例模式(提前加载):"+instance);
}
//由于类加载的特性,static属性只会加载一次,因此static属性的对象也是唯一的
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
延迟加载
延迟加载就需要保证多线程场景下的单例
new对象时必须保证只有一个线程进入创建单例
使用双重检查机制来保证单例唯一性
- 第一个if是为了判断是否需要让线程获取锁
- 获取完锁后进入同步代码块,再进入if判断一次单例是否被其它线程创建过
public class DelaySingleton {
public static void main(String[] args) {
DelaySingleton instance = DelaySingleton.getInstance();
System.out.println("单例模式(延迟加载):"+instance);
}
//全局单例
private static DelaySingleton instance;
//私有构造方法
private DelaySingleton(){}
//双重检查保证多线程下的单例唯一性
public static DelaySingleton getInstance() {
if (instance == null){
synchronized (DelaySingleton.class){
if (instance == null){
instance = new DelaySingleton();
}
}
}
return instance;
}
}
工厂模式
工厂模式的出现是为了抽离类中对象实例化部分的逻辑,使得类能更好的遵循单一职责原则。同时在新增子类时,也不需要重新修改创建实例的代码,创建新类型的逻辑交给了工厂负责,遵循了开闭原则。
工厂模式应用广泛,如Spring的bean工厂
工厂模式分类
简单工厂模式
- 静态工厂
- 反射工厂
- 实例工厂
抽象工厂模式
- 工厂方法模式
- 抽象工厂
工厂模式的具体实现方法有很多种,有时候我们可以根据场景需要灵活选择不同的实现方案来设计工厂模式,因此工厂模式并不存在非常严格的规定和准则。
我们需要知道的是工厂模式的核心就是由工厂类来负责合适对象的创建。
产品类
public interface Product {
String getName();
Product newInstance();
}
public class ProductA implements Product{
@Override
public String getName() {
return "产品A";
}
@Override
public Product newInstance() {
return new ProductA();
}
}
public class ProductB implements Product{
@Override
public String getName() {
return "产品B";
}
@Override
public Product newInstance() {
return new ProductB();
}
}
public class ProductC implements Product{
@Override
public String getName() {
return "产品C";
}
@Override
public Product newInstance() {
return new ProductC();
}
}
简单工厂
简单工厂只需要客户提供指明对象类型的参数,即可实例化具体的产品返回,返回的产品转换为基类型。
1.静态工厂
下面是一个最简单的静态工厂实现,但是如果需要一个新的产品类型D,我们就需要修改工厂代码,这打破了开闭原则,因此我们需要改进该静态工厂。
public class SimpleStaticFactory {
public enum ProductType{
A,B,C
}
public static Product create(ProductType productType){
if (productType.equals(ProductType.A)){
return new ProductA();
}
else if (productType.equals(ProductType.B)){
return new ProductB();
}
else if (productType.equals(ProductType.C)){
return new ProductC();
}
return null;
}
}
2.反射工厂
反射工厂模式需要运行时权限,因此在某些特定环境是无法实现的。同时反射机制也会降低程序的效率,在对性能要求很高的场景应该避免这种用法。
public class ClassFactory {
public static void main(String[] args) {
ClassFactory classStaticFactory = new ClassFactory();
//向静态工厂注册子类型
classStaticFactory.registerProduct("A", ProductA.class);
classStaticFactory.registerProduct("B", ProductB.class);
classStaticFactory.registerProduct("C", ProductC.class);
try {//创建实例
Product a = classStaticFactory.create("A");
Product b = classStaticFactory.create("B");
Product c = classStaticFactory.create("C");
System.out.println("简单工厂(反射):"+ a.getName());
System.out.println("简单工厂(反射):"+ b.getName());
System.out.println("简单工厂(反射):"+ c.getName());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
private Map<String,Class> registeredProducts = new HashMap<>();
//向工厂注册一种新的类型
public void registerProduct(String productId,Class<? extends Product> productClass){
registeredProducts.put(productId,productClass);
}
//通过反射动态创建类的实例
public Product create(String productId) throws IllegalAccessException, InstantiationException {
Class productClass = registeredProducts.get(productId);
return (Product) productClass.newInstance();
}
}
3.使用newInstance方法进行类注册的简单工厂
这种工厂的特点是提前注册,将子类的一个实例副本保存到集合中,同时所有子类需要实现一个newInstance()
来创建当前类的实例。工厂通过调用子类的newInstance()
方法来动态的创建子类实例。
import java.util.HashMap;
import java.util.Map;
public class NewInstanceFactory {
public static void main(String[] args) {
NewInstanceFactory newInstanceStaticFactory = new NewInstanceFactory();
//向工厂注册子类型
newInstanceStaticFactory.registerProduct("A", new ProductA());
newInstanceStaticFactory.registerProduct("B", new ProductB());
newInstanceStaticFactory.registerProduct("C", new ProductC());
Product a = newInstanceStaticFactory.create("A");
Product b = newInstanceStaticFactory.create("B");
Product c = newInstanceStaticFactory.create("C");
System.out.println("简单工厂(实例):"+ a.getName());
System.out.println("简单工厂(实例):"+ b.getName());
System.out.println("简单工厂(实例):"+ c.getName());
}
private Map<String,Product> registeredProducts = new HashMap<>();
//向工厂注册新的实例
public void registerProduct(String productId,Product product){
registeredProducts.put(productId,product);
}
//通过保存的实例创建新的实例
public Product create(String productId){
Product product = registeredProducts.get(productId);
if (product != null){
return product.newInstance();
}
return null;
}
}
抽象工厂模式
抽象工厂模式要求产品和工厂都是面向抽象类或接口的,每一个产品都有其父类,且交由具体的工厂实例化,所有具体的工厂都实现了抽象工厂类。
1.传统工厂方法模式
工厂方法模式是抽象工厂模式的特例,即只存在一个抽象产品类。通过一个抽象的工厂类提供创建实例的抽象方法,其创建实例的逻辑应交由具体的子类工厂方法实现。也就是如果我们需要一个新的产品加入工厂,需要继承抽象工厂并实现对应的创建方法,这遵循了开闭原则。
传统工厂方法模式就是只有一种抽象产品的抽象工厂
2.匿名工厂方法
匿名工厂方法即将抽象工厂类的具体实现通过匿名类的形式写在代码块中,同样遵循了开闭原则。
public abstract class ProductFactory {
public static void main(String[] args) {
//创建子类工厂
ProductFactory productAFactory = new ProductAFactory();
Product a = productAFactory.create();
System.out.println("工厂方法模式:"+a.getName());
//匿名具体工厂方法模式,创建实例C
ProductFactory productBFactory = new ProductFactory() {
@Override
protected Product create() {
return new ProductB();
}
};
Product b = productBFactory.create();
System.out.println("匿名具体工厂方法模式:"+b.getName());
}
//抽象方法,将具体创建实例的逻辑交由具体工厂类实现
protected abstract Product create();
}
具体的工厂类
public class ProductAFactory extends ProductFactory{
@Override
protected Product create() {
return new ProductA();
}
}
3.抽象工厂模式
抽象工厂模式是工厂方法模式的拓展,如果说工厂方法模式中只包含一个抽象产品类,那么抽象工厂模式则包含多个抽象产品类。
抽象工厂模式中,每个抽象产品都有一个抽象实例化方法,具体的产品类将交由具体工厂实现每个产品的实例化方法。
抽象工厂模式组成部分
AbstractFactory
(抽象工厂类):抽象类,用于声明创建不同类型产品的方法ConcreteFactory
(具体工厂类):具体类,用于实现抽象工厂中声明的方法AbstractProduct
(抽象产品类):抽象类或接口,一簇相关的产品类由来自不同层级的相似产品类组成。ProductA1和ProductB1来自第一个类簇,由ConcreteFactory1实例化。ProductA2和ProductB2来自第二个类簇,由ConcreteFactory2实例化。
抽象工厂模式使得产品和工厂都依赖了抽象,抽象工厂类只关心一系列抽象的产品,不关心具体的产品是什么,也不关心如何生产产品。
public abstract class AbstractFactory {
public static void main(String[] args) {
//创建具体工厂1
AbstractFactory factory1 = new ConcreteFactory1();
AbstractProductA productA1 = factory1.createProductA();
AbstractProductB productB1 = factory1.createProductB();
System.out.println("factory1创建的具体产品:"+productA1.getName());
System.out.println("factory1创建的具体产品:"+productB1.getName());
//创建具体工厂2
AbstractFactory factory2 = new ConcreteFactory2();
AbstractProductA productA2 = factory2.createProductA();
AbstractProductB productB2 = factory2.createProductB();
System.out.println("factory2创建的具体产品:"+productA2.getName());
System.out.println("factory2创建的具体产品:"+productB2.getName());
}
protected abstract AbstractProductA createProductA();
protected abstract AbstractProductB createProductB();
}
abstract class AbstractProductA {
protected abstract String getName();
}
abstract class AbstractProductB {
protected abstract String getName();
}
class ConcreteFactory1 extends AbstractFactory {
@Override
protected AbstractProductA createProductA() {
return new ProductA1();
}
@Override
protected AbstractProductB createProductB() {
return new ProductB1();
}
}
class ConcreteFactory2 extends AbstractFactory{
@Override
protected AbstractProductA createProductA() {
return new ProductA2();
}
@Override
protected AbstractProductB createProductB() {
return new ProductB2();
}
}
class ProductA1 extends AbstractProductA {
@Override
protected String getName() {
return "产品A1";
}
}
class ProductA2 extends AbstractProductA {
@Override
protected String getName() {
return "产品A2";
}
}
class ProductB1 extends AbstractProductB {
@Override
protected String getName() {
return "产品B1";
}
}
class ProductB2 extends AbstractProductB {
@Override
protected String getName() {
return "产品B2";
}
}
建造者模式
如果一个对象有多种实例化形式,最直接的方法就是构建多个构造函数,按照不同场景进行实例化,但缺少可读性和灵活性,因此不是最佳方案。
建造者模式的目的旨在对一个复杂对象的实例化操作逻辑分别进行封装,这些类就被称为建造者。每当需要来自同一个类但具有不同结构的对象时,就可以通过构造另一个建造者来进行实例化。
当需要不同结构的对象时,我们可以通过添加新的建造者,从而实现对修改的关闭对拓展的开放
常用的建造者模式如使用JwtBuilder
来构建token
建造者模式组成部分
Product
(产品类):需要为其构建对象的类,是具有不同表现形式的复杂或复合对象Builder
(抽象建造者类):用于声明构建产品类的组成部分的抽象类或接口,它的作用是仅公开构建产品类的功能,隐藏产品类的其它功能。(将产品类和构建产品类的其它类分离开)ConcreteBuilder
(具体建造者类):用于实现抽象建造者类接口中声明的方法,通过getResult()
方法返回构建好的产品类Director
(导演类):用于指导如何构建对象的类。在建造者模式的某些变体中,导演类已被移除,其角色被抽象建造者或客户端所替代。
带有导演类的建造者模式
产品类
public abstract class Product {
public abstract Double getPrice();
public abstract String getName();
public abstract Long getId();
public abstract String getDesc();
@Override
public String toString() {
return super.toString();
}
}
public class Product1 extends Product{
private String name;
private Long id;
private String desc;
private Double price;
public void addName(String name) {
this.name = name;
}
public void addId(Long id) {
this.id = id;
}
public void addDesc(String desc) {
this.desc = desc;
}
public void addPrice(Double price) {
this.price = price;
}
@Override
public Double getPrice() {
return price;
}
public String getName() {
return name;
}
public Long getId() {
return id;
}
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Product1{" +
"name='" + name + '\'' +
", id=" + id +
", desc='" + desc + '\'' +
", price=" + price +
'}';
}
}
public class Product2 extends Product{
private String name;
private Long id;
private String desc;
private Double price;
public void addName(String name) {
this.name = name;
}
public void addId(Long id) {
this.id = id;
}
public void addDesc(String desc) {
this.desc = desc;
}
public void addPrice(Double price) {
this.price = price;
}
@Override
public Double getPrice() {
return price;
}
@Override
public String getName() {
return name;
}
@Override
public Long getId() {
return id;
}
@Override
public String getDesc() {
return desc;
}
@Override
public String toString() {
return "Product2{" +
"name='" + name + '\'' +
", id=" + id +
", desc='" + desc + '\'' +
", price=" + price +
'}';
}
}
建造者类
public interface ProductBuilder {
void buildProduct();
void addName(String name);
void addId(Long id);
void addDesc(String desc);
void addPrice(Double price);
Product getProduct();
}
public class Product1Builder implements ProductBuilder{
private Product1 product;
@Override
public void buildProduct() {
product = new Product1();
}
@Override
public void addName(String name) {
product.addName(name);
}
@Override
public void addId(Long id) {
product.addId(id);
}
@Override
public void addDesc(String desc) {
product.addDesc(desc);
}
@Override
public void addPrice(Double price) {
product.addPrice(price);
}
@Override
public Product getProduct() {
return product;
}
}
public class Product2Builder implements ProductBuilder{
private Product2 product;
@Override
public void buildProduct() {
product = new Product2();
}
@Override
public void addName(String name) {
product.addName(name);
}
@Override
public void addId(Long id) {
product.addId(id);
}
@Override
public void addDesc(String desc) {
product.addDesc(desc);
}
@Override
public void addPrice(Double price) {
product.addPrice(price);
}
@Override
public Product getProduct() {
return product;
}
}
Director类
public class ProductBuilderDirector {
public static void main(String[] args) {
ProductBuilderDirector director = new ProductBuilderDirector();
Product product1 = director.buildProduct1(new Product1Builder());
Product product2 = director.buildProduct2(new Product2Builder());
System.out.println("director协助下完成product1的构建:"+product1);
System.out.println("director协助下完成product2的构建:"+product2);
}
public Product buildProduct1(ProductBuilder builder) {
builder.buildProduct();
builder.addId(System.currentTimeMillis());
builder.addName("product-1");
builder.addDesc("product-1 version 1.0");
builder.addPrice(3.5D);
return builder.getProduct();
}
public Product buildProduct2(ProductBuilder builder) {
builder.buildProduct();
builder.addId(System.currentTimeMillis());
builder.addName("product-2");
builder.addDesc("product-2 version 1.0");
builder.addPrice(8.5D);
return builder.getProduct();
}
}
问题思考
在建造者模式中,如果产品1和产品2的结构不同,它们对应的具体建造者类的方法也会不同。在这种情况下,可以考虑以下几种设计选择:
- 在抽象建造者类中声明所有方法:这种方式可以让
Director
类统一使用抽象建造者来构建产品,但是这样做会导致一些方法在某些具体建造者中是不必要的。例如,产品2的建造者可能不需要实现addPart1
方法。这种设计具有侵入性,因为它要求所有具体建造者类都必须实现抽象建造者中定义的所有方法。 - 使用接口隔离原则:可以为每个不同的产品建造者定义不同的接口,这样每个建造者只需要实现它们真正需要的方法。这样做可以减少侵入性,但是
Director
类在使用时需要知道具体的建造者类型,这可能会增加使用的复杂性。 - 使用可选的方法实现:在抽象建造者类中声明所有方法,但是提供一个默认的空实现(例如,在Java中可以使用接口的默认方法)。这样,具体建造者类可以选择性地覆盖它们需要的方法。这种方式既保持了
Director
类的统一性,又减少了对具体建造者的侵入性。 - 不在抽象建造者中声明特定方法:如果
addPart1
和addPart2
方法是特定于具体建造者的,那么它们不应该在抽象建造者中声明。Director
类可以通过其他方式来指导建造过程,例如通过使用工厂模式来获取正确的建造者实例。
具有方法链的匿名建造者
这是一种建造者模式的实现方式,方法链即链式调用,就是通过返回当前对象(this)来继续调用其本身。
下面通过将对象的构造者类构造为内部类的方式进行使用。
public class UseExample {
public static void main(String[] args) {
Product product = new Product.Builder()
.setName("product-1")
.setDesc("description for product-1")
.setPrice(2.9D)
.setType("type-1")
.setVersion("version 1.0")
.build();
System.out.println(product);
}
}
public class Product {
public static class Builder{
private Product product = new Product();
public Builder setName(String name){
product.setName(name);
return this;
}
public Builder setDesc(String desc){
product.setDesc(desc);
return this;
}
public Builder setPrice(Double price){
product.setPrice(price);
return this;
}
public Builder setType(String type){
product.setType(type);
return this;
}
public Builder setVersion(String version){
product.setVersion(version);
return this;
}
public Product build(){
return product;
}
}
private String name;
private String desc;
private Double price;
private String type;
private String version;
private Product(){}
private void setName(String name) {
this.name = name;
}
public void setDesc(String desc) {
this.desc = desc;
}
public void setPrice(Double price) {
this.price = price;
}
public void setType(String type) {
this.type = type;
}
public void setVersion(String version) {
this.version = version;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
", price=" + price +
", type='" + type + '\'' +
", version='" + version + '\'' +
'}';
}
}
原型模式
原型模式实际上是一种克隆对象的方法,在以下几种情况可能需要克隆已经实例化的对象
- 依赖于外部资源或硬件密集型操作进行新对象的创建的情况
- 获取相同对象在相同状态的拷贝而无须进行重复获取状态操作的情况
- 在不确定所属具体类型时需要对象的实例的情况
JavaScript的对象设计使用的就是原型模式
原型模式组成
Prototype
(抽象原型类):声明了clone()
方法的接口或基类,其中clone()
方法必须由派生对象实现,在简单场景中,并不需要这种基类,只需要具体的类即可
ConcretePrototype
(具体原型类):用于实现或扩展clone
方法的类,clone
方法必须要实现,因为它返回了类型的新实例。如果只在基类中实现了clone方法,却没在具体类中实现,那么当我们在具体原型类的对象上调用该方法时,会返回一个基类的抽象原型对象
浅拷贝和深拷贝
浅拷贝是指复制对象的属性值,如果遇到引用对象也只是复制地址
深拷贝则是在复制值的同时如果遇到引用对象,也要对引用对象进行深拷贝
Object作为抽象原型类,在Java语言中,所有的类都是Object的子类,在Object 中提供了克隆方法clone(),用于创建一个原型对象,其 clone()方法具体实现由JVM完成,用户在使用时无须关心。
浅拷贝原型模式
在Java中只有实现了Cloneable接口的类才能够使用clone()方法来进行复制,因此我们需要实现Cloneable。
public class ConcretePrototype implements Cloneable {
public static void main(String[] args) {
ConcretePrototype concretePrototype = new ConcretePrototype();
try {
ConcretePrototype prototype = (ConcretePrototype) concretePrototype.clone();
System.out.println("是否新对象:"+ (prototype != concretePrototype) );
System.out.println("是否浅拷贝:"+ (prototype.attachment == concretePrototype.attachment) );
System.out.println(prototype);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
private int anInt = 1;
private double aDouble = 2.2f;
private String string = "str";
private Attachment attachment = new Attachment();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "ConcretePrototype{" +
"anInt=" + anInt +
", aDouble=" + aDouble +
", string='" + string + '\'' +
", attachment=" + attachment +
'}';
}
}
class Attachment{
private String str = "attachment";
private boolean aBoolean = false;
@Override
public String toString() {
return "Attachment{" +
"str='" + str + '\'' +
", aBoolean=" + aBoolean +
'}';
}
}
深拷贝原型模式
通常java中我们可以通过序列化和反序列化来深拷贝一个对象,属性中需要序列化的类都要实现Serializable接口,不需要序列化拷贝的属性可以使用transient关键字,这样拷贝到的就是null
import java.io.*;
public class DeepCloneConcretePrototype implements Serializable {
private static final long serialVersionUID = 1236212586122492189L;
public static void main(String[] args) {
DeepCloneConcretePrototype deepCloneConcretePrototype = new DeepCloneConcretePrototype();
DeepCloneConcretePrototype prototype = null;
try {
prototype = deepCloneConcretePrototype.deepClone();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("是否新对象:"+ (prototype != deepCloneConcretePrototype) );
System.out.println("是否深拷贝:"+ (prototype.attachment != deepCloneConcretePrototype.attachment) );
System.out.println(prototype);
}
private DeepCloneConcretePrototype deepClone() throws IOException, ClassNotFoundException {
//序列化
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (DeepCloneConcretePrototype)ois.readObject();
}
private int anInt = 1;
private double aDouble = 2.2f;
private String string = "str";
private DeepCloneAttachment attachment = new DeepCloneAttachment();
@Override
public String toString() {
return "DeepCloneConcretePrototype{" +
"anInt=" + anInt +
", aDouble=" + aDouble +
", string='" + string + '\'' +
", attachment=" + attachment +
'}';
}
}
class DeepCloneAttachment implements Serializable{
private String str = "attachment";
private boolean aBoolean = false;
@Override
public String toString() {
return "DeepCloneAttachment{" +
"str='" + str + '\'' +
", aBoolean=" + aBoolean +
'}';
}
}
对象池模式
当我们处理封装外部资源的对象(如数据库连接)时,对象的创建造作会耗费很多资源
解决方案是重用和共享这些创建成本高昂的对象,这被称为对象池模式
对象池模式组成部分
ResourcePool
(资源池类):用于封装逻辑的类,用来保存和管理资源列表Resource
(资源类):用于封装特定资源的类,资源类通常被资源池类引用,因此只要资源池不重新分配,它们就永远不会被回收Client
(客户端类):使用资源的类
当客户端需要使用资源时,会向资源池类申请,资源池类检查后获取第一个可用资源并将其返回给客户端
客户端使用完资源后会释放资源,资源会重新回到资源池以便重复使用
资源池类
import java.util.ArrayList;
import java.util.List;
public class ResourcePool {
//可用资源列表
private List<Resource> available = new ArrayList<>();
//当前资源池创建被投入使用的资源列表
private List<Resource> inuse = new ArrayList<>();
//客户端获取资源
public Resource acquireResource(){
if (available.size() <= 0){
Resource resource = new Resource(this);
inuse.add(resource);
return resource;
}else {
//返回第一个可用资源
return available.remove(0);
}
}
//客户端释放资源
public void releaseResource(Resource resource) {
available.add(resource);
}
}
资源类
public class Resource {
private ResourcePool resourcePool;
public Resource(ResourcePool resourcePool) {
this.resourcePool = resourcePool;
}
//资源释放方法
public void release(){
resourcePool.releaseResource(this);
}
}
客户端类
public class Client {
public static void main(String[] args) {
ResourcePool resourcePool = new ResourcePool();
Resource resource = resourcePool.acquireResource();
System.out.println("当前使用资源:"+resource);
System.out.println("释放资源");
resource.release();
Resource resource1 = resourcePool.acquireResource();
System.out.println("当前使用资源:"+resource1);
Resource resource2 = resourcePool.acquireResource();
System.out.println("当前使用资源:"+resource2);
}
}
行为型模式(11种)
责任链模式
设想一个场景,需要对一批从客户端来的数据进行多种不同的操作,我们会使用多个不同的类负责不同的操作,而不是用一个类集成所有操作,这样做能让代码松耦合且简洁。
这些类被称为处理器,责任链模式让处理器按以下方式处理:如果需要则处理请求,否则将请求传递给下一个处理器。
责任链模式组成部分
Client
(客户端):客户端是使用责任链模式的应用程序的主要结构,它的职责是实例化一个处理器的链,然后在第一个对象中调用handleRequest
方法Handler
(处理器):这是一个抽象类,提供给所有实际处理器进行继承。它拥有一个handleRequest
方法,用来接收需要处理的请求ConcreteHandler
(具体处理器):这是一个实现了handleRequest
方法的具体类。每一个具体处理器都维持一个引用,指向链中下一个具体处理器,需要检查它自身是否能处理这个请求,不能就将请求传递给链中的下一个具体处理器。
链表式的责任链模式
每一个处理器需要实现一个方法,该方法被客户端所使用,并能够设置下一个处理器,当它无法处理请求时,将请求传给下一个处理器,这个方法可以加入到Handler
基类中
处理器接口
public interface Handler {
void handleRequest(Request request);
void setNextHandler(Handler handler);
}
具体处理器类
public class ConcreteHandler1 implements Handler {
private Handler handler;
@Override
public void handleRequest(Request request) {
if (canHandle(request)){
//处理请求
System.out.println(request.getName());
}else {
handler.handleRequest(request);
}
}
private boolean canHandle(Request request) {
if (request.getName().equals("请求类型1")){
return true;
}
return false;
}
@Override
public void setNextHandler(Handler handler) {
this.handler = handler;
}
}
public class ConcreteHandler2 implements Handler {
private Handler handler;
@Override
public void handleRequest(Request request) {
if (canHandle(request)){
//处理请求
System.out.println(request.getName());
}else {
handler.handleRequest(request);
}
}
private boolean canHandle(Request request) {
if (request.getName().equals("请求类型2")){
return true;
}
return false;
}
@Override
public void setNextHandler(Handler handler) {
this.handler = handler;
}
}
public class ConcreteHandler3 implements Handler {
private Handler handler;
@Override
public void handleRequest(Request request) {
if (canHandle(request)){
//处理请求
System.out.println(request.getName());
}else {
handler.handleRequest(request);
}
}
private boolean canHandle(Request request) {
if (request.getName().equals("请求类型3")){
return true;
}
return false;
}
@Override
public void setNextHandler(Handler handler) {
this.handler = handler;
}
}
请求类
public class Request {
private String name;
public Request(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
客户端类
public class Client {
public static void main(String[] args) {
//创建具体处理器
ConcreteHandler1 handler1 = new ConcreteHandler1();
ConcreteHandler2 handler2 = new ConcreteHandler2();
ConcreteHandler3 handler3 = new ConcreteHandler3();
//设置责任链
handler1.setNextHandler(handler2);
handler2.setNextHandler(handler3);
//处理请求
handler1.handleRequest(new Request("请求类型1"));
handler1.handleRequest(new Request("请求类型2"));
handler1.handleRequest(new Request("请求类型3"));
//处理请求-失败
handler1.handleRequest(new Request("请求类型100"));
}
}
问题思考
在具体处理器3也无法处理请求时,我们直接调用下一个处理器处理请求会报出空指针异常。由于处理器3就是最后一个处理器,因此我们需要一个默认处理器来处理无法被处理的请求,这是一种比较妥善的设计方式。
默认处理器是责任链中的最后一个处理器,用于处理无法被责任链处理的任意请求。
class Test{
public static void main(String[] args){
// 创建具体处理器
ConcreteHandler1 handler1 = new ConcreteHandler1();
ConcreteHandler2 handler2 = new ConcreteHandler2();
ConcreteHandler3 handler3 = new ConcreteHandler3();
DefaultHandler defaultHandler = new DefaultHandler(); // 创建默认处理器
// 设置责任链
handler1.setNextHandler(handler2);
handler2.setNextHandler(handler3);
handler3.setNextHandler(defaultHandler); // 将默认处理器设置为链的最后一个处理器
}
}
责任链模式还有另外一种形式,可以设置责任链中处理器的优先级,并使用数组来存储处理器,通过遍历处理器数组来执行责任链逻辑,例如Servlet中的Filter就是如此设计的
public final class ApplicationFilterChain implements FilterChain {
private int pos = 0; //当前执行filter的offset
private int n; //当前filter的数量
private ApplicationFilterConfig[] filters; //filter配置类,通过getFilter()方法获取Filter
private Servlet servlet;
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
} else {
// filter都处理完毕后,执行servlet
servlet.service(request, response);
}
}
}
命令模式
当存在一个场景需要大量的指令来触发不同的操作时,我们通常需要使用命令模式来构建
命令模式的目的
- 提供一个统一的方法来封装命令和其所需要的参数来执行一个动作
- 允许处理命令,例如将命令存储在队列中
命令模式组成部分
Command
(命令类):这是表示命令封装的抽象类,它声明了执行的抽象方法,该方法应该由所有具体命令实现。
ConcreteCommand
(具体命令类):这是命令类的实际实现,它必须执行命令并处理与每个具体命令相关的参数,它将命令委托给接收者。
Receiver
(接收者):这是负责执行与命令关联的操作的类。
Invoker
(调用者):这是触发命令的类,通常是外部事件,例如用户操作。
Client
(客户端):这是实例化具体命令对象及其接收者的实际类。
通常我们接收到外部类的触发条件,如Swing中按钮的各种事件,我们会使用if-else去判断处理具体的操作逻辑,这种场景我们就能把不同的操作逻辑抽象的触发条件抽象为不同的命令,通过接收者来执行具体的操作,这样子就达到了调用者(触发按钮事件的客户端)和接收者(实现命令接口的类)是分离的。
所有具体的触发条件都抽象为命令,我们只关心命令是否执行了,不关心命令具体如何执行
Command command = (Command) e.getSource();
command.execute();
命令接口
public interface Command {
void execute();
}
接收者类
public class CommandAReceiver {
public void operation() {
System.out.println("operation A done");
}
}
class CommandBReceiver {
public void operation() {
System.out.println("operation B done");
}
}
class CommandCReceiver {
public void operation() {
System.out.println("operation C done");
}
}
具体命令类
public class ConcreteCommandA implements Command{
private CommandAReceiver receiver;
public ConcreteCommandA(CommandAReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.operation();
}
}
class ConcreteCommandB implements Command{
private CommandBReceiver receiver;
public ConcreteCommandB(CommandBReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.operation();
}
}
class ConcreteCommandC implements Command{
private CommandCReceiver receiver;
public ConcreteCommandC(CommandCReceiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.operation();
}
}
调用者类
import java.util.ArrayList;
import java.util.List;
public class CommandInvoker {
private List<Command> commandList = new ArrayList<>();
//清空命令
public void clearCommands(){
commandList.clear();
}
//添加命令
public void addCommand(Command command){
commandList.add(command);
}
//执行命令
public void executeCommands(){
for (Command command : commandList) {
command.execute();
}
}
}
客户端类
public class Client {
public static void main(String[] args) {
CommandInvoker invoker = new CommandInvoker();
//添加具体的命令,每个命令都保留一份命令接收者的引用用于执行具体的操作
invoker.addCommand(new ConcreteCommandA(new CommandAReceiver()));
invoker.addCommand(new ConcreteCommandB(new CommandBReceiver()));
invoker.addCommand(new ConcreteCommandC(new CommandCReceiver()));
//执行命令
invoker.executeCommands();
}
}
解释器模式
解释器模式是一种特殊的设计模式,它用于定义一个语言的文法,并建立一个解释器来解释该语言中的句子。这种模式被用于编程语言的编译器和解释器中,但也可以用于实现任何特定类型的编码规则。
解释器模式组成部分
Context
(上下文):包含解释器之外的一些全局信息。AbstractExpression
(抽象表达式):声明一个所有具体表达式共享的接口,这个接口用于解释一个特定的上下文。TerminalExpression
(终结符表达式):实现与文法中的终结符相关联的解释操作。通常一个句子中的每个终结符需要一个TerminalExpression
。NonterminalExpression
(非终结符表达式):为文法中的非终结符实现解释操作。
// 抽象表达式
public interface Expression {
boolean interpret(String context);
}
// 终结符表达式
public class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context) {
return context.contains(data);
}
}
// 非终结符表达式
public class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
public class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
// 客户端
public class InterpreterPatternDemo {
// 规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
// 规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married woman? " + isMarriedWoman.interpret("Married Julie"));
}
}
迭代器模式
迭代器模式是Java中最广为认知的模式之一,Java程序员在使用Collection时,不需要关注其类型是数组,列表,集合还是其它。
我们可以以相同的方式处理集合,无论它是列表还是数组,这是因为它提供了一种迭代其元素而不暴露其内部结构的机制。更重要的是,不同类型的集合能够使用相同统一的机制,这种机制被称为迭代器模式。
迭代器模式提供了一种顺序遍历聚合对象元素而不暴露其内部实现的方法
迭代器模式组成部分
Aggregate
(抽象容器):应该由所有类实现的抽象类,并且可以由迭代器遍历。这对应于java.util.Collection
接口Iterator
(抽象迭代器):抽象迭代器是迭代器抽象类ConcreteAggregate
(具体容器):具体容器可以实现内部不同的结构,但会暴露处理器遍历容器的具体迭代器ConcreteIterator
(具体迭代器):这是处理特定具体容器类的具体迭代器。实际上,对于每个具体容器,必须实现一个具体迭代器
迭代器类可以嵌套在容器类中,因为迭代器需要访问容器的内部变量
迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
容器接口
public interface Aggregate {
}
容器以及迭代器具体实现
public class StringArray implements Aggregate{
public static void main(String[] args) {
StringArray stringArray = new StringArray(new String[]{"a", "b", "c"});
Iterator iterator = stringArray.iterator();
//遍历迭代器
while (iterator.hasNext()) {
String next = (String) iterator.next();
System.out.println(next);
}
}
private String values[];
public StringArray(String[] values) {
this.values = values;
}
public Iterator iterator(){
return new StringIterator();
}
private class StringIterator implements Iterator{
private int pos;
@Override
public boolean hasNext() {
return pos < values.length;
}
@Override
public Object next() {
return values[pos++];
}
}
}
如今迭代器在大部分语言中都很流行,当使用以下循环结构遍历集合时,它也在语言级别实现:
for (String item : strings)
System.out.println(item);
可以使用泛型机制来实现迭代器,这样子就可以避免强制转换生成的运行时异常
当需要具有特定行为的容器时,我们应该考虑扩展java collection包中实现的一个类,而不是创建一个新类
观察者模式
在实践中,模块是需要协调工作的。一个对象往往能够知道另外一个对象的变化。一般的解决方案是创建一个类不断轮询另外一个对象,查看其是否改变。更加智能的方法是使另外一个对象保留观察对象的引用,当其发生变化时通知观察者。但如果想得到良好的设计应当是让两者都依赖于抽象,而不是互相依赖。
RocketMQ
中的Topic也是基于观察者模式设计的,消费者作为Observer类订阅Topic,当Topic状态改变,消费者也会更新自身行为
目的
观察者模式使得一个对象的状态改变时,已经登记的其它对象能够观察到这一改变。
观察者模式的组成部分
Subject
(主题):主题通常是由类实现的可观察接口,应通知观察者使用attach方法注册。当它们不再需要被告知更改时,使用detach方法取消注册。ConcreteSubject
(具体主题):具体主题是一个实现主题接口的类,它处理观察者列表并更新它们的变化。Observer
(观察者):观察者是一个由对象实现的接口,应该根据主题中的更改来进行更新。每个观察者都应该实现update方法,该方法通知它们新的状态变化。ConcreteObserver
(具体观察者):具体观察者实现了观察者接口,是接收到通知后进行改变的类
使用场景
- 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
优点
解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。
缺点
在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。
主题接口
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObserver(String message);
}
具体主题类
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject implements Subject{
public static void main(String[] args) {
//创建可观察的具体类
Subject subject = new ConcreteSubject();
//注册观察者
subject.attach(new ConcreteObserver1());
subject.attach(new ConcreteObserver2());
subject.attach(new ConcreteObserver3());
//通知所有观察者
subject.notifyObserver("ConcreteSubject的状态改变了");
}
private List<Observer> observerList = new ArrayList<>();
@Override
public void attach(Observer observer) {
observerList.add(observer);
}
@Override
public void detach(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyObserver(String message) {
for (Observer observer : observerList) {
observer.update(message);
}
}
}
观察者接口
public interface Observer {
void update(String message);
}
具体观察者类
public class ConcreteObserver1 implements Observer{
@Override
public void update(String message) {
System.out.println("观察者1接收消息:"+message);
}
}
class ConcreteObserver2 implements Observer{
@Override
public void update(String message) {
System.out.println("观察者2接收消息:"+message);
}
}
class ConcreteObserver3 implements Observer{
@Override
public void update(String message) {
System.out.println("观察者3接收消息:"+message);
}
}
中介者模式
设想这样的场景,程序中一般都有互相通信的模块和对象,最简单的实现方式就是直接让它们彼此发送消息
但是这样容易造成混乱,且客户端需要维持多个连接,更好的解决方案是让服务器管理客户端之间的通信。
客户端将消息发送到服务器,服务器对客户端的所有连接都保持活动状态,并且可以向所有收件人广播消息。
中介者模式是迪米特法则的典型例子(保持对其它类的最少了解)
中介者模式和观察者模式的不同:中介者模式是双向通信的,观察者是类似发布订阅式的单向通信
中介者模式的目的也是对象之间的解耦
中介者模式组成部分
Mediator
(抽象中介者):抽象中介者定义了参与者的交互方式,在此接口或抽象类中声明的操作与场景相关ConcreteMediator
(具体中介者):它实现了中介者声明的操作(服务端)Colleague
(抽象同事角色):这是一个接口或抽象类,用于定义需要调解的参与者如何交互ConcreteColleague
(具体同事角色):具体实现类(客户端)
抽象中介者
public interface Mediator {
void register(String id,Colleague colleague);
void sendMessageTo(String id,String message);
}
具体中介者
import java.util.HashMap;
import java.util.Map;
public class ConcreteMediator implements Mediator{
private Map<String,Colleague> colleagues = new HashMap<>();
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();
Colleague c1 = new ConcreteColleague1(mediator);
Colleague c2 = new ConcreteColleague2(mediator);
Colleague c3 = new ConcreteColleague3(mediator);
mediator.register("c1",c1);
mediator.register("c2",c2);
mediator.register("c3",c3);
c1.sendTo("c2","hello c2,im c1");
c2.sendTo("c3","hello c3,im c2");
}
public void register(String id,Colleague colleague){
colleagues.put(id,colleague);
}
public void sendMessageTo(String id,String message){
Colleague colleague = colleagues.get(id);
if (colleague != null){
colleague.receive(message);
}
}
}
抽象同事角色
public interface Colleague {
void receive(String message);
void sendTo(String id, String message);
}
具体同事角色
public class ConcreteColleague1 implements Colleague{
private Mediator mediator;
public ConcreteColleague1(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void receive(String message) {
System.out.println(this+"接收到消息:"+message);
}
@Override
public void sendTo(String id, String message) {
mediator.sendMessageTo(id,message);
}
}
class ConcreteColleague2 implements Colleague{
private Mediator mediator;
public ConcreteColleague2(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void receive(String message) {
System.out.println(this+"接收到消息:"+message);
}
@Override
public void sendTo(String id, String message) {
mediator.sendMessageTo(id,message);
}
}
class ConcreteColleague3 implements Colleague{
private Mediator mediator;
public ConcreteColleague3(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void receive(String message) {
System.out.println(this+"接收到消息:"+message);
}
@Override
public void sendTo(String id, String message) {
mediator.sendMessageTo(id,message);
}
}
备忘录模式
有这样的场景,需要保存类内部的状态,以便能够在以后阶段恢复它。如果直接在类中实现这样的功能,类的功能就太复杂,违背了单一职责原则。同时,封装阻止我们直接访问需要记忆的对象的内部状态。
目的
备忘录模式用于保存对象的内部状态而不破坏其封装,并在以后阶段恢复其状态。
只要涉及执行回滚操作,就会使用备忘录模式。它可以用于各种原子事务,如果其中一个操作失败,则必须将对象恢复到初始状态。
备忘录模式组成部分
Originator
(发起者):发起者是我们需要记住状态的对象,以便在某个时刻恢复它。Caretaker
(管理者):这是负责触发发起者的变化或触发发起者返回先前状态的动作的类。Memento
(备忘录):这是负责存储发起者内部状态的类。备忘录提供了两种设置和获取状态的方法,但这些方法应该对管理者隐藏。
管理者类
public class Caretaker {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("state1");
//保存状态
Originator.Memento memento = originator.saveState();
//设置新状态
originator.setState("state2");
//恢复状态
originator.restoreState(memento);
System.out.println("当前状态为:"+originator.getState());
}
}
发起者类和内部备忘录类
//发起者是要被保存状态的类
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento saveState(){
return new Memento(state);
}
public void restoreState(Memento memento){
this.state = memento.getState();
}
//备忘录类作为静态内部类,以便发起者访问,同时内部方法对外界是不公开的
public static class Memento {
private final String state;
public Memento(String state) {
this.state = state;
}
private String getState(){
return state;
}
}
}
状态模式
状态模式是面向对象设计中有限状态机的实现,有限状态机代表了一个可以处于有限数量状态的抽象机器。
状态模式允许一个对象在其内部改变状态时,改变它的行为。
状态模式组成部分
Context
(上下文类):抽象的机器,拥有不同的状态,且在不同状态下有不同的行为State
(状态接口):抽象的状态,拥有一个handle方法来处理当前上下文,实现状态改变ConcreteState
(具体状态类):实现了状态接口的相关方法
状态模式的核心思想是将状态的变化逻辑封装在各个具体的状态类中,从而实现了行为与状态的分离。
上下文类
public class Context {
public static void main(String[] args) {
Context context = new Context(new ConcreteState1());
for (int i = 0; i < 10; i++) {
context.request();
}
}
//当前状态
private State state;
public Context(State state) {
this.state = state;
}
//请求改变状态
public void request(){
state.handle(this);
}
public void setState(State state) {
this.state = state;
}
}
状态接口
public interface State {
void handle(Context context);
}
具体状态类
public class ConcreteState1 implements State{
@Override
public void handle(Context context) {
System.out.println("状态1 - 触发事件1");
context.setState(new ConcreteState2());
}
}
class ConcreteState2 implements State{
@Override
public void handle(Context context) {
System.out.println("状态2 - 触发事件2");
context.setState(new ConcreteState3());
}
}
class ConcreteState3 implements State{
@Override
public void handle(Context context) {
System.out.println("状态3 - 触发事件3");
context.setState(new ConcreteState1());
}
}
策略模式
策略模式用于将不同的解决问题的步骤封装在同一个类中,而不是用一部分代码替换另外一部分代码。
策略模式在于将解决某一领域问题的不同解决方案进行抽象,在需要拓展新的解决方案时,只要实现新的策略即可,遵循了开闭原则
目的
策略模式定义了一系列算法,封装了每个算法,并使它们可以互换。
JavaSwing中的不同容器布局使用的就是策略模式设计
高级的策略模式做法还可以将策略和对于的条件放进hashMap中管理,这样就能完美消除if-else
策略模式组成部分
Strategy
(抽象策略):特定策略的抽象ConcreteStrategy
(具体策略):实现抽象策略的类Context
(环境):运行特定策略的类
环境类
public class Context {
//当前策略
private SortStrategy strategy;
public static void main(String[] args) {
Context context = new Context();
int[] array = {3,2,5,6,1,0,8,9};
context.setStrategy(new BubbleSortStrategy());
context.sortArray(array);
context.setStrategy(new InsertionSortStrategy());
context.sortArray(array);
context.setStrategy(new QuickSortStrategy());
context.sortArray(array);
}
private void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void sortArray(int[] array){
strategy.sort(array);
}
}
抽象策略类和具体策略类
public interface SortStrategy {
void sort(int[] array);
}
class BubbleSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
// 实现冒泡排序算法
System.out.println("使用冒泡排序");
// ...排序逻辑
}
}
class QuickSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
// 实现快速排序算法
System.out.println("使用快速排序");
// ...排序逻辑
}
}
class InsertionSortStrategy implements SortStrategy {
@Override
public void sort(int[] array) {
// 实现插入排序算法
System.out.println("使用插入排序");
// ...排序逻辑
}
}
策略模式还有一种比较特殊的特例叫做空对象模式
也就是一种策略类
NullClass
,它可以替换真实的Class,可以模拟什么都不做的情况
模板方法模式
顾名思义,模板方法模式为代码提供了一个模板,可以由实现不同功能的开发人员填写。如HTML模板,大部分网站都遵循了HTML模板,每个内容编写者都能使用此模板添加内容。
目的
使用模板模式的目的是为了避免写重复的代码,以便开发人员可以专注于核心逻辑。
模板模式实现的最好方式是使用抽象类。抽象类可以提供给我们所知道的实现区域,默认实现和为实现保持开放的区域。
模板方法组成部分
Template
(抽象模板类):一个模板抽象类,有一些默认实现的方法,还有一些开放待实现的抽象方法ConcreteTemplate
(具体模板类):实现模板类的具体类,实现了模板的抽象方法,同时也继承了默认实现的一些方法
public abstract class Template {
public static void main(String[] args) {
Template t1 = new Template1();
Template t2 = new Template2();
t1.info();
t2.info();
t1.doSomething();
t2.doSomething();
}
public void info(){
System.out.println("This class is a subclass of the Template class.");
}
public abstract void doSomething();
}
class Template1 extends Template{
@Override
public void doSomething() {
System.out.println("I am template1.");
}
}
class Template2 extends Template{
@Override
public void doSomething() {
System.out.println("I am template2.");
}
}
访问者模式
设想一个场景,在UI编程中有一个基本图形类,派生出三角形,正方形等图形类,我们想要为每个图形类增加一个save方法来保存图形数据。最直观的方案是在图形类基类中添加一个save抽象方法。
但如果我们现在要修改某个图形保存的数据格式怎么办?例如原来保存的是json格式,现在要改为xml格式。因此这种设计不遵循开闭原则,我们需要访问者模式解决这个问题。
目的
访问者模式将操作与其操作的对象分开,允许添加新操作而不更改结构类。
访问者模式组成部分
Element
(元素):表示对象结构的基类,结构中的所有类都是它派生的,定义了一个accept
方法,该方法接受一个访问者对象作为参数。ConcreteElement
(具体元素类):这是我们需要添加在Visitor类中实现的外部操作的具体类Visitor
(访问者):访问者类,它声明了与每个具体元素相对应的方法visit。方法名称相同,但重载参数不同。如果有需要也可以使用不同名称的命名方法。ConcreteVisitor
(具体访问者):实现访问者接口,提供了对每种类型的具体元素的具体访问操作,当需要一组单独操作时,只需要创建另外一个访问者。
访问者接口和具体访问者类
public interface Visitor {
void visit(ConcreteElementA concreteElementA);
void visit(ConcreteElementB concreteElementB);
}
class ConcreteVisitor1 implements Visitor{
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("访问者1访问元素A的逻辑");
concreteElementA.operationA();
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("访问者1访问元素B的逻辑");
concreteElementB.operationB();
}
}
class ConcreteVisitor2 implements Visitor{
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("访问者2访问元素A的逻辑");
concreteElementA.operationA();
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("访问者2访问元素B的逻辑");
concreteElementB.operationB();
}
}
元素接口和具体元素类
public interface Element {
void accept(Visitor visitor);
}
class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA(){
System.out.println("具体元素A的操作");
}
}
class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationB(){
System.out.println("具体元素B的操作");
}
}
客户端类
public class Client {
public static void main(String[] args) {
Element[] elements = {new ConcreteElementA(),new ConcreteElementB()};
Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
for (Element element : elements) {
element.accept(visitor1);
element.accept(visitor2);
}
}
}
结构型模式(7种)
适配器模式
适配器模式提供了一种代码重用的方案,即将已有的代码适配或者包装到一些新接口中,而这些新接口是在设计之初没有考虑到的。适配器通常用于处理遗留代码,通过包装现有代码,使其适配新代码的接口,就可以立即访问旧的。
该模式可以通过多继承的方式实现,例如实现多个接口,也可以使用旧对象成为类属性的组合来实现。适配器模式也被称为包装器。
目的
采用适配器模式的目的是将现有的旧接口转换成新的客户端接口,我们的目标是尽可能多地重用原来已经测试过的代码,并且可以对新接口自由地进行修改。
适配器模式组成部分
Client
(客户端):代码客户端Adapter
(适配器类):将调用转发给Adaptee
的适配器类OldClass
(旧代码类):需要适配的旧代码Target
(目标类):支持的新接口
目标类
public interface Target {
void newMethod();
}
旧代码类(要拓展新功能的类)
public class OldClass {
public void oldMethod(){
System.out.println("旧类方法");
}
}
适配器类
public class Adapter extends OldClass implements Target {
@Override
public void newMethod() {
System.out.println("新的接口方法");
}
}
客户端
public class Client {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.oldMethod();
adapter.newMethod();
}
}
代理模式
代理模式:
为一个对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象
代理模式有不同的形式,主要有三种 静态代理,动态代理(JDK代理、接口代理)和 Cglib代理(可以在内存多态的创建对象,而不需要实现接口,他是属于动态代理的范畴)
代理模式是干什么的?
代理模式就是为了完成对目标类的增强,完成对目标类的织入
代理模式增强的是切入点(目标类方法)
代理模式的组成部分
Subject
(共同接口):客户端使用的现有接口RealSubject
(真实对象):真实对象的类ProxySubject
(代理对象):代理类
下面是一个JDK动态代理的例子
共同接口
public interface Subject {
void method();
}
被代理类
public class RealSubject implements Subject {
@Override
public void method() {
System.out.println("方法被执行了");
}
}
代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxySubject {
public static void main(String[] args) {
//JDK动态代理
ProxySubject proxyCreator = new ProxySubject(new RealSubject());
Subject proxy = proxyCreator.createProxy();
proxy.method();
}
//被代理类
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
public Subject createProxy() {
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("增强前置方法");
Object o = method.invoke(realSubject, args);
System.out.println("增强后置方法");
return o;
}
};
return (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
invocationHandler);
}
}
装饰器模式
有时需要在现有代码中添加或删除一些功能,同时对现有的代码结构不会造成影响,并且这些删除或者增加的功能又不足以做成一个子类。这种情况下装饰器模式就会排上用场。
装饰器模式聚合了它将要装饰的原有对象,实现了与原有对象相同的接口,代理委托原有对象的所有公共接口调用,并在子类中实现新增的功能。
被装饰对象及其装饰器是可互换的,装饰器的接口必须完全符合被装饰对象的接口
装饰器模式可以配合策略模式使用,即将策略注入组件,从而扩展功能
目的
装饰器模式的目的是动态扩展现有对象的功能而不更改原有代码,它能够适配原始接口,并且使用组合而不是子类化来扩展功能
装饰器模式组成部分
Component
(抽象组件):抽象组件,可以是一个接口ComponentImpl
(组件实现类):我们想装饰的组件之一Decorator
(抽象装饰器):这是一个抽象的组件装饰器ConcreteDecorator
(具体装饰器):这是添加额外功能的组件装饰器
装饰器接口和抽象装饰器类
public interface Decorator {
void operation();
}
class AbstractDecorator implements Decorator{
private Component component;
public AbstractDecorator(Component component) {
this.component = component;
}
@Override
public void operation() {
//调用原始方法
component.originalMethod();
}
}
组件类
public interface Component {
void originalMethod();
}
组件实现类
public class ComponentImpl implements Component{
@Override
public void originalMethod() {
System.out.println("原始方法");
}
}
具体装饰器
public class ConcreteDecorator extends AbstractDecorator{
public static void main(String[] args) {
Component component = new ComponentImpl();
Decorator decorator = new ConcreteDecorator(component);
decorator.operation();
}
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
System.out.println("前置增强方法");
super.operation();
System.out.println("后置增强方法");
}
}
代理模式和装饰器模式的一些思考
代理模式的核心在于控制对对象的访问。它主要用于在不改变对象接口的情况下,为对象提供一个代理以控制对这个对象的访问。这通常涉及到对对象的访问进行管理或增强,比如远程对象访问、延迟初始化、访问控制和日志记录等。
装饰器模式的核心在于动态地添加功能。它允许在不改变对象接口的前提下,通过将对象包装进装饰类来为对象添加新的行为。这种模式强调的是扩展对象的功能,而不是控制对对象的访问。
代理模式和装饰器模式虽然都能对方法进行增强,但它们各自适合的场景有所不同:
代理模式适合的场景:
- 远程对象访问:当对象位于远程服务器上时,代理模式可以隐藏远程API的复杂性。
- 访问控制:当需要对原有对象的访问进行限制或检查时,如权限验证。
- 延迟初始化:当对象的创建开销很大时,代理模式可以延迟其创建,直到真正需要时。
- 智能引用:当需要对对象的生命周期进行管理时,代理模式可以在没有客户端引用时自动释放资源。
- 日志记录和监控:当需要记录对象的访问和操作时,代理模式可以在不改变对象代码的情况下添加日志记录功能。
装饰器模式适合的场景:
- 功能扩展:当需要给对象添加额外的功能,且这些功能可能在运行时动态变化时。
- 替代子类化:当子类化变得笨重或不可行时,装饰器模式提供了一种更为灵活的方式来扩展功能。
- 组合不同的行为:当需要组合多个行为到一个对象上时,装饰器模式允许通过不同的装饰器组合来实现。
- 可配置的系统:在需要构建一个支持大量可选行为和配置的系统时,装饰器模式允许客户端选择他们需要的装饰。
桥接模式
桥接模式通过将一个大类或多个紧密相关的类分为两个独立的层次结构,即抽象部分和实现部分,来实现解耦。在桥接模式中,抽象部分定义了高层逻辑,而实现部分则提供了底层的具体实现。通过将这两个部分分离,并使用桥接模式连接它们,可以在不修改现有代码的情况下方便地扩展和修改系统。
目的
桥接模式的目的是将抽象与实现解耦,使得二者可以独立地变化。它通过在公共接口和实现中使用继承来达到目的。
桥接模式组成部分
Abstraction
(抽象类):抽象化给出的定义,并保存一个对实现化对象的引用。Implementation
(实现类接口):这个是具体实现类的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象类的接口定义相同,实际上,这两个接口可以非常不一样。具体实现类应当只给出底层操作,而抽象类应当只给出基于底层操作的更高一层的操作。Refined
(扩充的抽象类):扩展抽象化角色,改变和修正父类对抽象化的定义。SpecificImpl
(具体实现类):这个角色给出实现类接口的具体实现。
实现接口和实现类
//实现类接口
public interface Implementation {
void operation();
}
//具体实现类A
class SpecificImplA implements Implementation {
@Override
public void operation() {
System.out.println("具体实现操作A");
}
}
//具体实现类B
class SpecificImplB implements Implementation {
@Override
public void operation() {
System.out.println("具体实现操作B");
}
}
抽象类和扩展抽象类
public abstract class Abstraction {
protected Implementation impl;
public Abstraction(Implementation impl) {
this.impl = impl;
}
public abstract void operation();
}
//扩充抽象类,可以水平扩充
abstract class Refined extends Abstraction{
public Refined(Implementation impl) {
super(impl);
}
public void extendedOperation(){
System.out.println("抽象类拓展的方法");
}
@Override
public void operation() {
impl.operation();
}
}
//具体类,继承扩充的抽象类
class Concrete extends Refined {
public Concrete(Implementation impl) {
super(impl);
}
@Override
public void operation() {
extendedOperation();
super.operation();
}
}
客户端
public class Client {
public static void main(String[] args) {
Implementation implA = new SpecificImplA();
Implementation implB = new SpecificImplB();
Abstraction abstractionA = new Concrete(implA);
Abstraction abstractionB = new Concrete(implB);
abstractionA.operation();
abstractionB.operation();
}
}
组合模式
组合模式的目的是将对象组合成树形或图形结构,使得用户对单个对象和组合对象的使用具有一致性。客户端代码不需要知道节点是单个对象(叶子节点)还是组合对象(具有子节点的节点,如根节点),并对其进行统一处理。
组合模式使用了递归组合机制,因此在处理组合对象时也是递归式的进行处理
如文件系统就可以使用组合模式,如Spring的
beanFactory
管理的可以是复合组件,也能是普通组件
优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
组合模式组成部分
Component
(抽象节点):它的主要作用是为树叶节点和复合节点声明公共接口,并实现它们的默认行为。Leaf
(叶子节点):是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。Composite
(复合节点):是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含Add()
、Remove()
、GetChild()
等方法。
抽象节点
public interface Component {
void operation();
}
复合节点
import java.util.ArrayList;
import java.util.List;
public class Composite implements Component{
public static void main(String[] args) {
Composite composite = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
composite.addChild(leaf1);
composite.addChild(leaf2);
composite.addChild(leaf3);
composite.operation();
}
private List<Component> children = new ArrayList<>();
public void addChild(Component component){
children.add(component);
}
public void removeChild(Component component){
children.remove(component);
}
@Override
public void operation() {
for (Component child : children) {
child.operation();
}
}
}
叶子节点
public class Leaf implements Component{
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("叶子组件"+name);
}
}
外观模式
许多复杂的系统可以化简为几个子系统暴露的用例接口,这样可以让客户端代码不需要知道子系统的内部结构与联系,这被称为外观模式。
目的
外观模式的目的是为复杂的子系统提供单一的统一接口。通过为最重要的用例提供接口,能够简化大型和复杂系统的使用。
外观模式的核心就是通过封装隐藏复杂系统的内部细节,开放简单的核心接口给用户使用
外观模式组成部分
Client
(客户端类):子系统客户端代码。Facade
(外观接口):子系统接口。Subsystem
(子系统类):子系统中定义的类。
外观接口
public interface Facade {
void operation();
}
class FacadeImpl implements Facade{
private Subsystem1 subsystem1;
private Subsystem2 subsystem2;
private Subsystem3 subsystem3;
public FacadeImpl(Subsystem1 subsystem1, Subsystem2 subsystem2, Subsystem3 subsystem3) {
this.subsystem1 = subsystem1;
this.subsystem2 = subsystem2;
this.subsystem3 = subsystem3;
}
@Override
public void operation() {
subsystem1.operation1();
subsystem2.operation2();
subsystem3.operation3();
}
}
子系统类
public class Subsystem1{
void operation1(){
System.out.println("子系统方法1");
}
}
class Subsystem2{
void operation2(){
System.out.println("子系统方法2");
}
}
class Subsystem3{
void operation3(){
System.out.println("子系统方法3");
}
}
客户端类
public class Client {
public static void main(String[] args) {
FacadeImpl facade = new FacadeImpl(new Subsystem1(), new Subsystem2(), new Subsystem3());
facade.operation();
}
}
享元模式
创建对象需要花费时间和资源。最好的例子是创建Java常量字符串如Boolean.valueOf(boolean b)
或Character valueOf(char c)
,因为从不创建实例,它们返回的是不可变的缓存实例。
内部状态(Intrinsic State):
- 内部状态是存储在享元对象内部的信息,不会随环境的改变而改变。
- 这些状态对于对象的行为是不变的,因此可以在多个对象之间共享,从而节省内存。
外部状态(Extrinsic State):
- 外部状态是随环境变化而变化的状态,不能共享。
- 客户端代码在使用享元对象时,需要提供外部状态。这些状态通常影响对象的行为。
目的
享元模式的目的是通过在相似对象之间共享状态来减少内存占用。只有把数量庞大的对象减少到少数具有代表性、不依赖于对象相等的对象,且它们的状态能够被外部变化,才能实现这一目的。
享元模式组成部分
Client
(客户端):客户端代码FlyweightFactory
(享元工厂类):如果享元对象不存在则创建它们,如果存在则返回它们。Flyweight
(抽象享元类)ConcreteSharableFlyweight
(具体可共享享元类):与其同伴共享状态的对象ConcreteUnshareableFlyweight
(具体不可共享享元类):不共享其状态的享元对象。
抽象享元类(接口)
public interface Flyweight {
void operation(String extrinsicState);
}
具体可共享享元类
public class ConcreteSharableFlyweight implements Flyweight{
private final String intrinsicState;
public ConcreteSharableFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("Intrinsic State = " + intrinsicState + ", Extrinsic State = " + extrinsicState);
}
}
享元工厂
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class FlyweightFactory {
private Map<String,Flyweight> flyweightMap = new ConcurrentHashMap<>();
public Flyweight getFlyweight(String key){
return flyweightMap.computeIfAbsent(key,k -> new ConcreteSharableFlyweight(k));
}
}
客户端
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight fw1 = factory.getFlyweight("state1");
Flyweight fw2 = factory.getFlyweight("state1");
fw1.operation("state1");
fw2.operation("state2");
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com