设计模式的奇妙世界:揭秘23种设计模式

程序设计的几大原则是一组指导性的规则,它们旨在帮助开发人员编写可维护、可扩展、可复用和易于理解的代码。以下是几个常见的程序设计原则:

  1. 开闭原则(Open-Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。意味着当需要添加新功能时,应该通过扩展现有代码而不是修改代码来实现。这样可以避免对现有功能的破坏,并且使代码更加稳定和可维护。
  2. 单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个原因来进行修改。每个类应该只负责一项职责,即只有一个引起它变化的原因。这样可以增加类的可读性、可维护性和重用性。
  3. 里氏替换原则(Liskov Substitution Principle,LSP):子类对象能够替换掉父类对象并且不影响程序的正确性。也就是说,子类应该能够被当作父类的类型使用,而不会导致程序出错或产生意外的行为。
  4. 依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于底层模块,二者都应该依赖于抽象。抽象不应该依赖于具体实现细节,具体实现细节应该依赖于抽象。这样可以解耦模块之间的依赖关系,提高系统的灵活性和可维护性。
  5. 接口隔离原则(Interface Segregation Principle,ISP):客户端不应依赖于它不使用的接口。一个类不应该强迫其客户端实现它们不需要的接口。应该将大接口拆分成较小、更具体的接口,以便客户端只需知道与其相关的接口。
  6. 合成复用原则(Composition/Aggregation Reuse Principle,CARP):尽量使用对象的组合和聚合的方式,而不是继承来达到代码复用的目的。通过组合和聚合可以更灵活、更高效地复用代码,同时减少了类之间的耦合关系。

设计模式是极其重要的软件开发理念,它帮助我们编写可重用且易维护的代码。

创建型模式

创建型设计模式是设计模式中的一种类别,主要关注如何在创建对象时增加灵活性和可重用性。这类设计模式主要包括以下五种模式:

工厂模式(Factory Pattern)

它提供了一种在不直接调用构造函数但仍能创建对象的方式。在工厂模式中,对象的创建被委托给一个工厂类的方法,这个方法作为一个接口来创建并返回对象。

工厂模式有以下特点:

  1. 工厂模式是一种创建单个对象的模式,事实上,它是一种被委托的对象。
  2. 使用工厂模式,我们可以通过一个接口创建新的实例,而不用直接调用一个类的构造函数。

简单工厂模式(下述示例也是静态工厂方法,即静态方法创建产品,是工厂方法的简化使用):

interface Animal {
    fun speak()
}

class Dog : Animal {
    override fun speak() {
        println("Dog says woof")
    }
}

class Cat : Animal {
    override fun speak() {
        println("Cat says meow")
    }
}

class AnimalFactory {
    companion object {
        fun createAnimal(type: String): Animal? {
            return when {
                "Dog".equals(type, ignoreCase = true) -> Dog()
                "Cat".equals(type, ignoreCase = true) -> Cat()
                else -> null
            }
        }
    }
}

fun main(args: Array<String>) {
    val dog = AnimalFactory.createAnimal("Dog")
    dog?.speak()
    val cat = AnimalFactory.createAnimal("Cat")
    cat?.speak()
}

工厂方法模式:

interface Animal {
    fun speak()
}

class Dog : Animal {
    override fun speak() {
        println("Dog says woof")
    }
}

class Cat : Animal {
    override fun speak() {
        println("Cat says meow")
    }
}

interface AnimalFactory {
    fun createAnimal(type: String): Animal?

    companion object {
        fun getFactory(): AnimalFactory {
            return impl
        }

        private val impl = AnimalFactoryImpl()
    }
}

class AnimalFactoryImpl : AnimalFactory {
    override fun createAnimal(type: String): Animal? {
        return when {
            "Dog".equals(type, ignoreCase = true) -> Dog()
            "Cat".equals(type, ignoreCase = true) -> Cat()
            else -> null
        }
    }
}

fun main(args: Array<String>) {
    val factory = AnimalFactory.getFactory()
    factory.createAnimal("Dog")?.speak()
    factory.createAnimal("Cat")?.speak()
}

工厂模式工厂方法模式是创建型设计模式,都被用来解决在代码中创建对象的问题。虽然它们看上去很相似,但还是有一些关键的区别。

  1. 工厂模式:也被称为简单工厂模式,它定义了一个用于创建对象的接口,这个接口的实现延迟到子类中进行。在简单工厂的类图中,通常包含以下角色:简单工厂(Simple Factory)、抽象产品(Product Interface)、具体产品(Concrete Product)
  2. 工厂方法模式:这是一种实现了“工厂”概念的设计模式,它提供了一种方法,客户端可以通过这个方法来创建对象,而无需直接使用new关键字创建。工厂方法模式中,通常包含以下角色:抽象工厂(Abstract Factory)、具体工厂(Concrete Factory)、抽象产品(Abstract Product)、具体产品(Concrete Product)

工厂模式和工厂方法模式之间的主要区别在于其处理对象创建的复杂程度:

  • 在工厂模式中,创建对象的逻辑在一个方法中。当需要创建新的对象类型时,必须修改这个方法,这违反了“开放-封闭原则”。
  • 而工厂方法模式通过定义创建对象的接口并将对象创建的逻辑推迟到子类中,使得添加新的对象类型不需要修改现有的代码,实现了“开放-封闭原则”。每个产品由其各自的工厂创建,这就意味着每个工厂都具有创建单一产品的责任和知识。这符合“单一职责原则”。

所以,总结起来,工厂模式适用于规模较小的应用,或者具体产品类型较少的情况,因为所有的创建逻辑都集中在一个工厂类中。而在需要大量创建产品,且需要大量的创建逻辑,同时也需要满足开放-封闭原则和单一职责原则时,工厂方法模式是更好的选择。

  1. 工厂方法可以隐藏创建产品的细节,且不一定每次都会真正创建产品,完全可以返回缓存的产品,从而提升速度并减少内存消耗。

  2. 总是引用接口而非实现类,能允许变换子类而不影响调用方,即尽可能面向抽象编程。

抽象工厂模式(Abstract Factory Pattern)

为创建一组相关或相互依赖的对象提供了一个接口,而无需指定其具体类。

抽象工厂模式由以下几个关键组成部分:

  1. 抽象工厂(Abstract Factory):定义了一个创建产品对象的接口,具体工厂类将实现这个接口来创建具体的产品对象。
  2. 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体的产品对象。
  3. 抽象产品(Abstract Product):定义了产品对象的接口,具体产品类将实现这个接口。
  4. 具体产品(Concrete Product):实现了抽象产品接口,具体的产品类将通过具体工厂来创建。

抽象工厂模式的好处在于,它能够提供一种将客户端代码与具体的对象创建过程解耦的方式,客户端代码只需通过抽象工厂接口来创建产品,而无需关心具体的产品类。

抽象工厂示例

interface AbstractFactory {
    fun createHtml(md: String): HtmlDocument
    fun createWord(md: String): WordDocument

    companion object {
        fun createFactory(name: String): AbstractFactory {
            return if (name == "fast") {
                FastFactory()
            } else {
                GoodFactory()
            }
        }
    }
}

interface HtmlDocument {
    fun toHtml()
    fun save(path: Path)
}

interface WordDocument {
    fun save(path: Path)
}

class FastHtmlDocument(md: String) : HtmlDocument {
    override fun toHtml() {
    }

    override fun save(path: Path) {
    }

}

class FastWordDocument(md: String) : WordDocument {
    override fun save(path: Path) {
    }
}

class FastFactory : AbstractFactory {
    override fun createHtml(md: String): HtmlDocument {
        return FastHtmlDocument(md)
    }

    override fun createWord(md: String): WordDocument {
        return FastWordDocument(md)
    }

}

class GoodHtmlDocument(md: String) : HtmlDocument {
    override fun toHtml() {
    }

    override fun save(path: Path) {
    }

}

class GoodWordDocument(md: String) : WordDocument {
    override fun save(path: Path) {
    }

}

class GoodFactory : AbstractFactory {
    override fun createHtml(md: String): HtmlDocument {
        return GoodHtmlDocument(md)
    }

    override fun createWord(md: String): WordDocument {
        return GoodWordDocument(md)
    }

}

抽象工厂模式具有以下几个优势:

  1. 封装了对象的创建过程:抽象工厂模式封装了对象的创建过程,客户端代码只需要通过抽象工厂接口来获取产品对象,无需关心具体的创建细节。这样使得客户端代码与具体对象的创建过程解耦,提高了代码的灵活性和可维护性。
  2. 符合开闭原则:抽象工厂模式符合开闭原则,当需要增加新的产品族时,只需添加对应的具体工厂类和产品类,而无需修改已有的代码。这样可以保持原有代码的稳定性和可靠性。
  3. 保持了产品族的一致性:抽象工厂模式能够确保一系列相关的产品对象被一起创建,从而保持了产品族的一致性。具体工厂类会创建一系列同一产品族的相关产品对象,这些产品对象之间具有一定的关联性或依赖性。这样可以确保这些产品对象之间的兼容性和一致性。
  4. 易于替换和扩展:由于抽象工厂模式将具体对象的创建过程封装在工厂类中,因此在需要替换或扩展具体对象时,只需修改具体工厂类即可。这样可以轻松地替换工厂类来创建不同的产品对象,或者扩展工厂类以创建更多的产品对象。这种可扩展性使得抽象工厂模式适用于变化频繁的系统。
  5. 对高层模块的透明性:由于客户端只与抽象工厂和抽象产品进行交互,无需了解具体的工厂和产品,因此高层模块对于具体的类是透明的。这样能够降低模块之间的耦合度,提高系统的灵活性和可维护性。

综上所述,抽象工厂模式通过封装对象的创建过程,保持产品族的一致性,提供了易于替换和扩展的能力,同时对高层模块透明,使得系统更加灵活、可维护和可扩展。

单例模式(Singleton Pattern)

这种类型的设计模式涉及到一个类,它负责创建自己的对象,同时确保只有一个对象被创建。这个类提供了一种访问其唯一的对象的方法,可以直接访问,不需要实例化该类的对象。

单例模式有以下几个关键特点:

  1. 私有的构造函数:为了确保类只有一个实例,单例模式将类的构造函数设为私有的,这样外部代码无法直接通过构造函数创建实例。
  2. 静态成员变量:单例模式会在类的内部创建一个静态成员变量,用于存储类的唯一实例。
  3. 静态获取方法:单例模式提供一个静态的获取方法,用于访问类的唯一实例。这个方法通常被命名为getInstance()
  4. 延迟初始化:单例模式通常会采用延迟初始化的方式创建实例,即在首次访问时才会创建实例。这样可以在需要时才创建实例,避免资源浪费。
/**
 * 饿汉模式(类加载时完成初始化)
 * 类加载较慢,但获取对象的速度快
 * 避免了多线程的同步问题
 * 如果从始至终未使用过这个实例,则会造成内存的浪费
 */
class Singleton private constructor() {
    companion object {
        private val INSTANCE = Singleton()
        fun getInstance() = INSTANCE
    }
}

/**
 * 懒汉式(线程不安全)
 * 声明静态对象,并在调用时初始化
 * 在第一次加载时需要实例化,反应慢些,且在多线程时不能正常工作
 */
class Singleton private constructor() {
    companion object {
        private var INSTANCE: Singleton? = null
        fun getInstance(): Singleton? {
            if (INSTANCE == null) {
                INSTANCE = Singleton()
            }
            return INSTANCE
        }
    }
}

/**
 * 懒汉式(线程安全)
 * 每次调用时进行同步,会造成不必要的开销
 */
class Singleton private constructor() {
    companion object {
        private var INSTANCE: Singleton? = null
        fun getInstance(): Singleton? {
            synchronized(Singleton::class.java) {
                if (INSTANCE == null) {
                    INSTANCE = Singleton()
                }
                return INSTANCE
            }
        }
    }
}

/**
 * 双重检查DCL
 */
class Singleton private constructor() {
    companion object {
        @Volatile
        private var INSTANCE: Singleton? = null
        fun getInstance(): Singleton? {
            //减少不必要的同步
            if (INSTANCE == null) {
                synchronized(Singleton::class.java) {
                    //volatile可见性,为null再创建
                    if (INSTANCE == null) {
                        INSTANCE = Singleton()
                    }
                }
            }
            return INSTANCE
        }

    }
}

/**
 * 静态内部类
 * 只有在调用方法时才会初始化实例,不仅能够保证线程安全,也能够保证唯一实例
 */
class Singleton private constructor() {
    companion object {
        fun getInstance() = SingleHolder.instance

        class SingleHolder {
            companion object {
                val instance = Singleton()
            }
        }
    }

    //反序列化
    @Throws(ObjectStreamException::class)
    fun readResolve(): Singleton {
        return getInstance()
    }
}

/**
 * 枚举类的每个枚举都是单例
 * 反序列化时会绕过普通类的private构造方法从而创建出多个实例
 * 提供readResolve方法一控制对象反序列化
 */

enum class Singleton6 {
    INSTANCE;

    fun doSomeThing() {

    }
}

在双重检查锁定方式中使用 @Volatile 关键字来修饰 instance 变量,主要是为了确保多个线程能正确地处理 instance 的可见性。

在Java中,每个线程都有自己的工作内存,工作内存是CPU缓存中的一部分,用于存储线程执行时所需的数据。当一个线程修改了某个变量的值时,它并不会立即将新值写回主内存,而是先在自己的工作内存中进行操作,之后再将修改后的值刷新回主内存。这个过程中,其他线程可能无法立即感知到该变量的新值,导致出现可见性问题。

而使用 @Volatile 关键字修饰 instance 变量后,可以确保以下两个特性:

  1. 可见性:当一个线程修改了被 volatile 修饰的变量的值时,会立即将新值刷新回主内存,使其他线程能够立即感知到该变量的最新值。
  2. 禁止指令重排序:JVM在编译和执行代码时可能会进行指令重排序,这种重排序在单线程环境下不会影响最终结果,但在多线程环境下可能导致程序的行为变得不可预测。而使用 volatile 关键字修饰的变量禁止了指令重排序,保证了程序的正确性。

在双重检查锁定方式中,通过使用 @Volatile 关键字修饰 instance 变量,可以确保多个线程正确地处理 instance 的可见性。当一个线程第一次访问 getInstance() 方法时,会判断 instance 是否为 null,如果是 null,则进入同步块并创建实例,然后将实例赋值给 instance 变量。这个实例在赋值后会立即被刷新回主内存,使其他线程能够感知到该实例的存在。而在之后的访问中,由于 instance 不为 null,就可以直接返回已经创建好的实例,避免了加锁带来的性能开销。

总之,使用 @Volatile 关键字修饰 instance 变量可以确保多个线程正确处理 instance 的可见性,从而在双重检查锁定方式中实现线程安全的单例模式。

这种设计模式通常用于需要全局访问且只有一个实例的场景,比如日志记录器、数据库连接池等。

构建者模式(Builder Pattern)

适用于构建复杂对象,当一个对象需要多个步骤或组合步骤构建时,可以使用构建者模式来简化构建过程,并且可以避免过多的构造器参数和多个构造器的问题。

下面是构建者模式的核心角色:

  1. 产品(Product):表示最终构建的对象,包含多个组成部分。
  2. 抽象构建者(Builder):定义了抽象的构建方法来构建产品的每个部分,以及一个获取产品的方法。
  3. 具体构建者(Concrete Builder):实现了抽象构建者接口,具体构建产品的每个部分,并提供一个获取产品的方法。
  4. 指挥者(Director):调用具体构建者的方法来构建产品,不直接与产品交互。(不一定要有)
class URI(
    private val domain: String,
    private val schema: String,
    private val query: MutableMap<String, String>
) {
    override fun toString() = StringBuilder().run {
        if (schema.isNotEmpty()) append(schema).append("//")
        if (domain.isNotEmpty()) append(domain).append("?")
        if (query.isNotEmpty()) {
            query.forEach { (key, value) ->
                append("$key=$value").append("&")
            }
        }
        //移除最后一个多余的&
        removeSuffix("&")
        toString()
    }
}

interface URIBuilder {
    fun domain(domain: String): URIBuilder
    
    fun schema(schema: String): URIBuilder

    fun query(query: MutableMap<String, String>): URIBuilder

    fun build(): URI

    companion object {
        fun createBuilder(): URIBuilder {
            return impl
        }

        private val impl = URIBuilderImpl()
    }
}

class URIBuilderImpl : URIBuilder {
    private var domain = ""
    private var schema = ""
    private var query = mutableMapOf<String, String>()
    
    override fun domain(domain: String): URIBuilderImpl {
        this.domain = domain
        return this
    }

    override fun schema(schema: String): URIBuilder {
        this.schema = schema
        return this
    }

    override fun query(query: MutableMap<String, String>): URIBuilderImpl {
        this.query = query
        return this
    }

    override fun build(): URI {
        return URI(this.domain, this.schema, this.query)
    }
}

fun main() {
    val uri = URIBuilder.createBuilder()
        .schema("https:")
        .domain("www.liaoxuefeng.com")
        .query(mutableMapOf("a" to "123", "q" to "K&R"))
        .build()
    println(uri)
}

使用构建者模式来构建复杂对象有几个好处:

  1. 简化对象构建过程:构建复杂对象可能涉及多个步骤或者组合步骤,使用构建者模式可以将这些步骤分离出来,使得构建过程更加清晰和可控。每个具体构建者负责完成特定的构建步骤,通过指挥者调用具体构建者的方法来按照特定顺序构建对象,从而简化了构建过程。
  2. 避免过多的构造器参数:当一个对象拥有多个属性,并且这些属性之间存在一定的依赖关系时,直接使用构造器来构建对象可能会导致构造器参数数量过多的问题,使得代码难以维护和理解。而构建者模式可以通过调用具体构建者的方法来设置每个属性,从而避免了过多的构造器参数,使得代码更加简洁和可读性更高。
  3. 灵活地构建不同的表示:在构建者模式中,同样的构建过程可以构建不同的表示,通过调用不同的具体构建者实现类来构建不同的产品对象。这使得我们可以使用相同的构建过程来构建不同属性组合的对象,从而灵活地构建不同的表示。
  4. 保持对象的不可变性:构建者模式可以通过设置属性的可见性或者使用只读属性来保持对象的不可变性,即对象在构建完成后不能再修改。这样可以确保构建后的对象在多线程环境下的线程安全性,减少并发访问的问题。

总之,使用构建者模式可以简化复杂对象的构建过程,避免过多的构造器参数,灵活地构建不同的表示,并且保持对象的不可变性。这使得代码更加清晰、可维护性更高,并且支持构建复杂对象的灵活性和线程安全性。

原型模式(Prototype Pattern)

这种模式实现了一个原型接口,该接口用于创建当前对象的克隆,当直接创建对象的代价大时,可以采用此种设计模式。通过使用原型模式,我们可以通过复制现有对象的原型来创建新的对象,而不是通过实例化类来创建。

原型模式的核心概念是原型(Prototype),它是一个被克隆的对象,充当了模板的角色。原型可以是一个接口、抽象类或具体类。

原型模式的关键步骤如下:

  1. 定义原型接口:如果创建的对象属于某个抽象类或接口的子类,需要首先定义一个原型接口来声明克隆方法。该克隆方法用于复制原型对象,返回一个新的对象副本。
  2. 实现具体原型类:原型接口的具体实现类实现克隆方法来提供对象的复制功能。具体原型类要实现对象的克隆,可以通过浅拷贝或深拷贝来创建新的对象副本。
  3. 克隆对象:通过调用具体原型类的克隆方法来创建新的对象。可以通过调用克隆方法或者使用克隆工具函数来实现对象的克隆。

原型模式的优点包括:

  1. 灵活性:通过克隆现有对象来创建新对象,可以避免依赖具体类的实例化操作,从而增加了灵活性。
  2. 性能优化:相比于实例化对象,克隆方法往往更快,可以提高对象的创建效率。
  3. 简化对象的创建过程:通过原型模式,可以更简单地创建一个对象,只需要克隆原型对象即可,无需关心创建过程的细节。
class Student {
    private var id: Int = 0
    private var name: String = ""

    fun copy(): Student {
        return Student().also {
            it.id = id
            it.name = name
        }
    }
}

Java的Object提供了一个clone()方法,它的意图就是复制一个新的对象出来,我们需要实现一个Cloneable接口来标识一个对象是“可复制”的 ,但是返回类型要强制转型 。

public class Student implements Cloneable, Serializable {
    private int id = 0;
    private String name = "";

    public Student() {
        System.out.println("Default Constructor called.");
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Student stu = new Student();
        System.out.println(stu.clone() != stu);
    }

    // 深拷贝方法
    public static <T extends Serializable> T deepCopy(T object) throws IOException, ClassNotFoundException {
        // 将对象写入字节流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(object);

        // 从字节流读取对象
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        return (T) objectInputStream.readObject();
    }
}

虽然原型模式有很多优点,但也要注意潜在的问题:

  1. 深拷贝问题:如果对象的属性包含了引用类型的成员变量,那么在原型模式中需要做深拷贝,以确保克隆的对象和原型对象完全独立,而不是共享相同的引用。
  2. 对象的构造函数不会被执行:通过克隆原型对象创建新对象时,对象的构造函数不会被执行。如果对象的构造函数中包含了一些关键逻辑或资源分配操作,那么在克隆过程中需要特别注意。

总之,原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,无需依赖具体类的实例化操作。它可以提供灵活性、性能优化和简化对象创建过程等优点,但要注意深拷贝和构造函数不会被执行的问题。

结构型模式

结构型模式是设计模式中的一类模式,它关注如何将类和对象组合成更大的结构以实现新的功能和行为。结构型模式通过定义类和对象之间的关系,来实现系统中不同部分之间的协作和组合。

在软件设计中,结构型模式通过提供灵活的机制,帮助我们构建一致、可扩展和易于维护的代码结构。下面介绍几种常见的结构型模式:

适配器模式(Adapter Pattern)

将一个类的接口转换为另一个客户端所期望的接口。这种模式用于解决由于接口不兼容而导致的两个类无法正常工作的问题。

适配器模式通过引入一个适配器对象,充当两个不兼容类之间的中间层,将一个类的接口转换为另一个类所期望的接口。适配器包装了需要适配的对象,将其接口转换为另一个目标接口。这样,客户端就可以通过适配器对象与被适配的对象进行交互,而不需要直接使用不兼容的接口。

适配器模式通常涉及以下几个角色:

  • 目标接口(Target Interface):定义客户端所期望的接口,即客户端希望与之交互的对象的接口。
  • 被适配者(Adaptee):需要被适配的旧接口或类。
  • 适配器(Adapter):将被适配者的接口转换为目标接口的适配器对象。
// 目标接口
interface MediaPlayer {
    fun play(filename: String)
}

// 被适配者(旧的英文播放器)
class EnglishMediaPlayer {
    fun playEnglish(filename: String) {
        println("Playing English music: $filename")
    }
}

// 适配器(将英文播放器适配成中文播放器)
class ChineseMediaPlayerAdapter(private val englishPlayer: EnglishMediaPlayer) : MediaPlayer {
    override fun play(filename: String) {
        // 这儿可以是预处理...
        // 使用适配器调用旧的英文播放器的方法
        englishPlayer.playEnglish(filename)
    }
}

// 客户端代码
fun main() {
    // 创建英文播放器对象
    val englishPlayer = EnglishMediaPlayer()

    // 创建中文播放器适配器对象
    val adapter = ChineseMediaPlayerAdapter(englishPlayer)

    // 使用中文播放器播放音乐
    adapter.play("中国之声.mp3")
}

适配器模式的优点包括:

  • 提供了兼容性:通过适配器,客户端可以使用不兼容的接口与目标对象进行交互。
  • 实现了解耦:适配器将客户端与被适配的类解耦,使得二者可以独立进行开发和演化。
  • 可复用性:适配器可以重复使用,适配不同的类或接口。

适配器模式在实际应用中有很多场景,例如:

  • 将第三方库或组件的接口转换为系统内部所期望的接口。
  • 适配不同版本或不同厂商的类或接口。
  • 封装老的业务逻辑接口,以提供给新系统使用。

装饰器模式(Decorator Pattern)

动态地给一个对象添加一些额外的职责,即扩展对象的功能。装饰器模式在不改变原始对象结构的情况下,通过包装对象来增强其功能。

装饰器模式通过将对象包装在装饰器类中,以实现对对象的功能扩展,同时保持对象接口的一致性。

装饰器模式通过组合和委托来实现功能的扩展,而不是通过继承来修改对象行为。装饰器类和被装饰的对象共同实现相同的接口,使得客户端可以透明地使用被装饰对象和装饰器对象。

装饰器模式涉及以下几个角色:

  • 抽象组件(Component):定义了被装饰对象和装饰器对象的共同接口,可以是抽象类或接口。
  • 具体组件(Concrete Component):实现了抽象组件的接口,是需要被装饰的对象。
  • 抽象装饰器(Decorator):继承了抽象组件,并持有一个抽象组件的引用。实现了抽象组件接口,并在方法中调用被装饰对象的方法,以及添加新的功能。
  • 具体装饰器(Concrete Decorator):继承了抽象装饰器,具体实现了新的功能。

装饰器模式在实际应用中有很多场景,比如:

  • 动态添加额外的功能,而不影响现有代码的结构,比如添加日志记录、性能监测等。
  • 对已有的类进行功能扩展,而不需要修改原始类的代码。
  • 实现对象透明的功能组合,以避免类的爆炸性增长。
// 抽象组件
interface TextNode {
    fun setText(text: String)
    fun getText(): String
}

// 具体组件
class SpanNode(private var text: String) : TextNode {
    override fun setText(text: String) {
        this.text = text
    }

    override fun getText(): String {
        return "<span>$text</span>"
    }

}

// 抽象装饰器
abstract class NodeDecorator(private var target: TextNode) : TextNode {
    override fun setText(text: String) {
        this.target.setText(text)
    }
}

// 具体装饰器
class BoldDecorator(private var target: TextNode) : NodeDecorator(target) {
    override fun getText(): String {
        return "<b>" + this.target.getText() + "</b>";
    }

}

fun main() {
    BoldDecorator(SpanNode("1234")).also { println(it.getText()) }
}

装饰器模式和适配器模式是两种不同的设计模式,它们有不同的作用和使用场景,下面是它们的区别:

  1. 目的和作用:
    • 装饰器模式(Decorator Pattern)的主要目的是在不修改现有对象接口的情况下,动态地添加功能或修改行为。它通过包装原始对象并使用相同的接口,以透明的方式扩展对象的功能。
    • 适配器模式(Adapter Pattern)的主要目的是将一个类的接口转换成客户端所期望的另一个接口。它通过包装一个已有的类,并提供一个兼容的接口,使得原本不兼容的接口能够一起工作。
  2. 主要应用场景:
    • 装饰器模式适用于以下情况:
      • 需要动态添加功能或修改对象的行为,而不希望通过修改对象的源代码来实现;
      • 需要在不影响其他对象的情况下,对单个对象或对象集合进行功能扩展;
      • 需要对一个对象进行多层次的功能扩展。
    • 适配器模式适用于以下情况:
      • 需要将一个类的接口转换成客户端所期望的另一个接口;
      • 需要复用一些现有的类,但是它们的接口与系统的要求不一致;
      • 需要解决不同类之间接口不兼容的问题。
  3. 关于对象关系:
    • 装饰器模式包含了原始对象和装饰器对象之间的聚合关系,装饰器对象通过持有原始对象的引用,以及实现相同的接口,来包装原始对象并添加额外的功能。
    • 适配器模式包含了被适配对象和适配器对象之间的委托关系,适配器对象通过持有被适配对象的实例,来将其接口转换成客户端所期望的接口。
  4. 代码修改方式:
    • 装饰器模式不需要修改原始对象的源代码,可以在运行时动态地为对象添加或修改功能。
    • 适配器模式需要修改被适配对象的源代码,使其与适配器接口兼容。

总结起来,装饰器模式用于包装和扩展对象的功能,而适配器模式用于将一个对象的接口转换成另一个接口。装饰器模式主要关注对对象功能的扩展,而适配器模式主要关注不同接口之间的兼容性。两种模式在设计目标、应用场景和代码实现上有明显的区别。

代理模式(Proxy Pattern)

为其他对象提供一种代理以控制对这个对象的访问。代理模式常用于限制对敏感或远程对象的直接访问,或者延迟对象的创建和初始化。

代理模式主要有三个角色:

  1. 抽象主题(Subject):定义了代理对象和真实对象的共同接口,这样代理对象可以在任何使用真实对象的地方替代它。
  2. 真实主题(Real Subject):实现了抽象主题接口,是需要被代理的真实对象。
  3. 代理(Proxy):持有一个真实主题的引用,并实现了抽象主题接口。代理对象可以控制对真实主题的访问,它可以在调用真实主题之前或之后执行其他操作。

代理模式的主要目的是控制对对象的访问,它可以在不改变真实对象的情况下,对其进行增强或保护。代理对象可以担任多种角色,比如远程代理、虚拟代理、保护代理等。

// 抽象主题
interface Image {
    fun display()
}

// 真实主题
class RealImage(private val filename: String) : Image {
    init {
        loadFromDisk()
    }

    private fun loadFromDisk() {
        println("从磁盘加载图片: $filename")
    }

    override fun display() {
        println("显示图片: $filename")
    }
}

// 代理
class ProxyImage(private val filename: String) : Image {
    private var realImage: RealImage? = null

    override fun display() {
        if (realImage == null) {
            realImage = RealImage(filename)
        }
        realImage?.display()
    }
}

// 客户端代码
fun main() {
    val image: Image = ProxyImage("image.jpg")

    // 第一次调用会创建真实对象并加载图片
    image.display()

    // 第二次调用只会直接显示图片,不会再次加载
    image.display()
}

代理模式在实际应用中有很多场景,比如:

  • 远程代理:用于在不同的地址空间中代理对象,使得客户端可以像访问本地对象一样访问远程对象。
  • 虚拟代理:用于延迟加载对象,当真正需要使用对象时才创建,以提高性能。
  • 保护代理:用于控制对对象的访问权限,可以限制某些客户端对对象的操作。
  • 缓存代理:用于缓存对象的结果,当下一个请求需要相同的结果时,直接返回缓存的结果而不是重新计算。

Decorator模式让调用者自己创建核心类,然后组合各种功能, 而Proxy模式决不能让调用者自己创建再组合,否则就失去了代理的功能。 Proxy模式让调用者认为获取到的是核心类接口,但实际上是代理类。

桥接模式(Bridge Pattern)

将抽象部分与实现部分分离,使它们可以独立地变化。桥接模式通过将抽象与实现解耦,使得它们可以独立地进行修改和扩展,而不会相互影响。

桥接模式主要通过将抽象部分与实现部分分离来实现解耦。它包括两个主要角色:

  1. 抽象部分(Abstraction):定义抽象部分的接口,并维护一个对实现部分的引用。抽象部分可以包含一些抽象的操作,它们依赖于实现部分的具体实现。
  2. 实现部分(Implementor):定义实现部分的接口,并提供实现部分的具体实现。

通过将抽象部分和实现部分分离,桥接模式可以使它们可以独立地变化。抽象部分和实现部分可以分别扩展,而且它们的扩展是独立的。这种灵活性使得桥接模式在以下情况下特别有用:

  • 当有多种抽象和多种实现的组合时,可以使用桥接模式将它们进行组合,并通过接口进行交互。
  • 当需要在运行时选择不同的具体实现时,可以使用桥接模式。
    桥接模式示例
abstract class Car(protected var engine: Engine) {
    abstract fun drive()
}

interface Engine {
    fun start()
}

abstract class RefinedCar(engine: Engine) : Car(engine) {
    override fun drive() {
        engine.start()
        println("Drive " + getBrand() + " car...")
    }

    abstract fun getBrand(): String
}

class BossCar(engine: Engine) : RefinedCar(engine) {
    override fun getBrand(): String {
        return "Boss"
    }

}

/**
 * 汽车=商标+发动机,可单独增加
 */

class HybridEngine : Engine {
    override fun start() {
        println("Start Hybrid Engine...")
    }

}

fun main() {
    BossCar(HybridEngine()).drive()
}

组合模式(Composite Pattern)

将对象组合成树形结构以表示"部分-整体"的层次结构。通过使用组合模式,可以一致地处理单个对象和组合对象,使得客户端可以将它们视为相同的实体进行操作。

组合模式由以下几个主要角色组成:

  1. 组件(Component):定义了组合模式中所有对象的通用接口,可以是抽象类或接口。组件角色可以拥有子组件,并声明一些通用操作。
  2. 叶子(Leaf):表示组合中的叶子节点对象,它不能拥有子组件,通常实现了组件接口定义的操作。
  3. 容器(Composite):表示组合中的容器节点对象,它可以拥有叶子节点和其他容器节点,通常实现了组件接口定义的操作。容器节点可以对其子节点进行增加、删除和遍历操作。
  4. 客户端(Client):使用组合模式的客户端代码,通过组合模式的接口来操作组件。
    组合模式示例
// 组件
interface Node {
    fun add(node: Node): Node
    fun children(): List<Node>
    fun toXml(): String
}

// 容器
class ElementNode(private val name: String) : Node {
    private val list = ArrayList<Node>()
    
    override fun add(node: Node): Node {
        list.add(node)
        return this
    }

    override fun children(): List<Node> {
        return list
    }

    override fun toXml(): String {
        val start = "<$name>\n"
        val end = "</$name>\n"
        val sj = StringJoiner("", start, end)
        list.forEach { node ->
            sj.add(node.toXml() + "\n")
        }
        return sj.toString()
    }
}

// 叶子
class TextNode(private val text: String) : Node {
    override fun add(node: Node): Node {
        throw UnsupportedOperationException()
    }

    override fun children(): List<Node> {
        return arrayListOf()
    }

    override fun toXml(): String {
        return text
    }
}

// 叶子
class CommentNode(private val text: String) : Node {
    override fun add(node: Node): Node {
        throw UnsupportedOperationException()
    }

    override fun children(): List<Node> {
        return arrayListOf()
    }

    override fun toXml(): String {
        return "<!-- $text -->"
    }
}

fun main() {
    val root: Node = ElementNode("school")
    root.add(ElementNode("classA")
            .add(TextNode("Tom"))
            .add(TextNode("Alice")))
    root.add(ElementNode("classB")
            .add(TextNode("Bob"))
            .add(TextNode("Grace"))
            .add(CommentNode("comment...")))
    println(root.toXml())
}

使用组合模式的主要优点是:

  1. 统一处理对象:组合模式使得客户端可以统一处理单个对象和组合对象,无需区分它们的类型,从而简化了客户端代码。
  2. 灵活性和扩展性:组合模式可以动态地增加、删除或替换组件,从而使系统具有更高的灵活性和扩展性。
  3. 简化使用界面:组合模式提供了一致的操作接口,使客户端使用起来更加简单方便。

享元模式(Flyweight Pattern)

通过共享对象来减少系统中对象的数量,从而减少内存占用和提高性能。享元模式适用于存在大量相似对象的场景,通过共享相同的状态,可以节省内存开销。

在享元模式中,对象可分为两类:内部状态(Intrinsic State)和外部状态(Extrinsic State)。

  • 内部状态是对象共享的部分,它不依赖于对象所处的上下文环境,并且可以在多个对象之间共享。在享元模式中,内部状态由享元对象自身管理和维护。
  • 外部状态是对象的上下文环境和变化的部分,它不能被共享。外部状态需要在使用享元对象时通过参数传递给享元对象。

使用享元模式的主要目的是通过共享对象来减少内存消耗,特别是在大量对象存在且对象的内部状态是相对固定的情况下。通过共享内部状态,可以减少对象的数量,提高系统性能。

享元模式由以下几个主要角色组成:

  • 享元接口(Flyweight):定义了享元对象的通用接口,声明了需要对外开放的方法。
  • 具体享元(Concrete Flyweight):实现了享元接口,表示可共享的具体享元对象。
  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,维护享元池(Flyweight Pool),确保对象的共享和重用。
// 享元接口
interface Shape {
    fun draw(color: String)
}

// 具体享元类
class Circle : Shape {
    private var x: Int = 0
    private var y: Int = 0

    override fun draw(color: String) {
        println("Drawing a $color circle at ($x, $y)")
    }

    fun setPosition(x: Int, y: Int) {
        this.x = x
        this.y = y
    }
}

// 享元工厂类
class ShapeFactory {
    private val circleMap: MutableMap<String, Circle> = hashMapOf()

    fun getCircle(color: String): Circle {
        var circle = circleMap[color]

        if (circle == null) {
            circle = Circle()
            circleMap[color] = circle
        }

        return circle
    }
}

// 客户端代码
fun main() {
    val shapeFactory = ShapeFactory()

    val circle1 = shapeFactory.getCircle("Red")
    circle1.setPosition(10, 20)
    circle1.draw("Red")

    val circle2 = shapeFactory.getCircle("Blue")
    circle2.setPosition(30, 40)
    circle2.draw("Blue")

    val circle3 = shapeFactory.getCircle("Red")
    circle3.setPosition(50, 60)
    circle3.draw("Red")
}

单例不是不变,是不允许创建新实例 ;享元要求实例不变,所以才能把"应该创建一个新实例"的操作给优化成"直接返回一个缓存的实例"。

外观模式(Facade Pattern)

提供一个统一的接口,用于访问系统中的一组接口。外观模式简化了客户端与子系统之间的交互,隐藏了子系统的复杂性,并提供了更简单和更清晰的接口。

外观模式将一个复杂的子系统封装在一个外观类中。外观类提供了一个高层次的接口,用于与子系统进行交互,而客户端则可以通过使用外观类来访问系统,而无需了解系统的内部实现细节。

外观模式由以下几个主要角色组成:

  • 外观(Facade):外观类是客户端与子系统之间的接口,封装了对子系统的访问。外观类知道哪些子系统负责处理特定的请求,并将客户端的请求委托给适当的子系统对象。
  • 子系统(Subsystem):子系统包含了系统中的各个组件和功能模块,是实现系统功能的具体实现类。子系统类并不知道外观类的存在,它们仅负责处理外观类委派的任务。
class Company {
}

class AdminOfIndustry {
    fun register(name: String): Company {
        return Company()
    }
}

class Bank {
    fun openAccount(companyID: String) {
    }
}

class Taxation {
    fun applyTaxCode(companyID: String) {
    }
}

class Facade {
    private val admin = AdminOfIndustry()
    private val bank = Bank()
    private val tax = Taxation()
    
    fun openCompany(name: String): Company {
        val c: Company = this.admin.register(name)
        val bankAccount = this.bank.openAccount(name)
        val taxCode = this.tax.applyTaxCode(name)
        return c
    }
}

行为型模式

行为模式(Behavioral Patterns)是一种设计模式的分类,专注于对象之间的通信和责任分配。行为模式关注的是对象之间的相互作用和协作,以实现系统的特定行为和功能。

行为模式主要解决对象间的通信问题,在不同的对象之间定义算法和责任分配的方式,以实现灵活、可扩展的系统。

常见的行为模式包括:

观察者模式(Observer Pattern)

定义了一种一对多的依赖关系,当一个对象的状态发生改变时,它的所有依赖者都会收到通知并更新自己。

在观察者模式中,有两个核心角色:观察者(Observer)和被观察者(Subject)。

  • 观察者(Observer):观察者是订阅了被观察者的通知,当被观察者的状态发生改变时,观察者会得到通知并执行相应的操作。
  • 被观察者(Subject):被观察者维护了一系列观察者对象,并提供注册、注销和通知的方法。当被观察者的状态改变时,它会通知所有的观察者。
// 观察者接口
interface Observer {
    fun update(stock: Int)
}

// 被观察者类
class Product {
    private val observers: MutableList<Observer> = mutableListOf()
    private var stock: Int = 0

    fun addObserver(observer: Observer) {
        observers.add(observer)
    }

    fun removeObserver(observer: Observer) {
        observers.remove(observer)
    }

    fun setStock(stock: Int) {
        this.stock = stock
        notifyObservers()
    }

    private fun notifyObservers() {
        for (observer in observers) {
            observer.update(stock)
        }
    }
}

// 具体观察者类
class User : Observer {
    override fun update(stock: Int) {
        println("User: Stock updated, current stock is $stock")
        // 执行其他操作...
    }
}

观察者模式的使用场景包括:当一个对象的改变需要同步更新其他对象时,或者需要将消息广播给多个对象时,观察者模式可以很好地解决这些问题。观察者模式实现了松耦合的对象之间的交互,使得被观察者和观察者可以独立地变化。

状态模式(State Pattern)

允许对象在内部状态改变时改变它的行为,看起来好像是改变了它的类。

状态模式的核心思想是将对象的状态封装成独立的状态对象,每个状态对象都实现了一组与该状态相关的操作。原始对象(也称为"上下文")在运行时会根据当前的状态对象来执行相应的操作,从而实现状态的切换和相关操作的执行。

状态模式的主要角色包括:

  • 上下文(Context):上下文是拥有状态的对象,它在运行时会根据当前的状态对象来执行相应的操作。上下文可以通过设置和更改状态对象来实现状态的切换。
  • 抽象状态(Abstract State):抽象状态是状态对象的抽象基类或接口,定义了状态对象的公共接口和方法,以及可以进行的操作。所有具体的状态类都必须实现抽象状态的接口。
  • 具体状态(Concrete State):具体状态是抽象状态的具体实现类,每个具体状态类都表示对象的不同状态,在具体状态类中实现了该状态下的相应操作。
sealed interface State {
    fun init(): String
    fun reply(input: String): String
}

object DisconnectedState : State {
    override fun init(): String {
        return "Bye!"
    }

    override fun reply(input: String): String {
        return ""
    }
}

object ConnectedState : State {
    override fun init(): String {
        return "Hello, I'm Bob."
    }

    override fun reply(input: String): String {
        if (input.endsWith("?")) {
            return "Yes. " + input.substring(0, input.length - 1) + "!";
        }
        if (input.endsWith(".")) {
            return input.substring(0, input.length - 1) + "!";
        }
        return input.substring(0, input.length - 1) + "?";
    }

}

class BotContext {
    private var state: State = DisconnectedState
    fun chat(input: String): String {
        if ("hello" == input) {
            // 收到hello切换到在线状态:
            state = ConnectedState
            return state.init()
        } else if ("bye" == input) {
            // 收到bye切换到离线状态:
            state = DisconnectedState
            return state.init()
        }
        return state.reply(input)
    }
}

fun main() {
    val scanner = Scanner(System.`in`)
    val bot = BotContext()
    while (true) {
        print("> ")
        val input: String = scanner.nextLine()
        val output: String = bot.chat(input)
        println(if (output.isEmpty()) "(no reply)" else "< $output")
    }
}

状态模式的优点包括:

  • 将对象的状态封装成独立的类,使得状态的变化与核心业务逻辑分离,方便维护和扩展。
  • 状态模式符合开闭原则,当需要添加新的状态时,只需要添加新的具体状态类即可,而无需修改现有的代码。
  • 状态模式遵循单一职责原则,每个状态类只关注自身的行为和状态转换逻辑,使得每个类的职责清晰明确。

策略模式(Strategy Pattern)

定义一系列算法,将每个算法封装起来,并使它们可以互换使用,使算法的变化独立于使用算法的客户端。

策略模式的核心思想是将算法的定义和使用进行解耦,将算法封装成单独的策略类,客户端根据需要选择合适的策略类,进行相应的操作。策略模式通过组合而不是继承来实现算法的变化,使得算法的变化对客户端透明。

策略模式的主要角色包括:

  • 策略接口(Strategy):策略接口定义了具体策略类必须实现的方法,它用于统一所有策略类的行为接口。
  • 具体策略类(Concrete Strategy):具体策略类实现了策略接口,并提供了具体的算法实现。
  • 上下文类(Context):上下文类持有一个策略接口的引用,并在需要时调用策略接口的方法进行具体的操作。
// 策略接口
interface PaymentStrategy {
    fun calculateAmount(amount: Double): Double
}

// 具体策略类:支付宝支付
class AlipayStrategy : PaymentStrategy {
    override fun calculateAmount(amount: Double): Double {
        return amount * 0.9 // 享受 9 折优惠
    }
}

// 具体策略类:微信支付
class WechatPayStrategy : PaymentStrategy {
    override fun calculateAmount(amount: Double): Double {
        return amount * 0.95 // 享受 95 折优惠
    }
}

// 具体策略类:银行卡支付
class CardPayStrategy : PaymentStrategy {
    override fun calculateAmount(amount: Double): Double {
        return amount // 不享受任何优惠
    }
}

// 上下文类:商品订单
class Order(private val paymentStrategy: PaymentStrategy) {
    fun calculateTotalAmount(amount: Double): Double {
        return paymentStrategy.calculateAmount(amount)
    }
}

// 客户端代码
fun main() {
    val amount = 100.0
    val alipayStrategy = AlipayStrategy()
    val wechatPayStrategy = WechatPayStrategy()
    val cardPayStrategy = CardPayStrategy()

    val order1 = Order(alipayStrategy)
    println("支付宝支付金额:${order1.calculateTotalAmount(amount)}")

    val order2 = Order(wechatPayStrategy)
    println("微信支付金额:${order2.calculateTotalAmount(amount)}")

    val order3 = Order(cardPayStrategy)
    println("银行卡支付金额:${order3.calculateTotalAmount(amount)}")
}

命令模式(Command Pattern)

将请求封装成对象,以便可以用不同的请求对客户端进行参数化。

命令模式的核心思想是将请求的发送者和接收者之间的联系解耦,通过引入命令对象作为中间层,实现请求的发送和执行的解耦。命令对象包含了请求的具体信息和执行方法,接收者负责具体的执行逻辑。

命令模式的主要角色包括:

  • 命令接口(Command):命令接口定义了具体命令类必须实现的方法,通常包括执行(execute)和撤销(undo)等方法。
  • 具体命令类(Concrete Command):具体命令类实现了命令接口,并提供了具体的操作逻辑和执行方法。
  • 命令发送者(Invoker):命令发送者负责创建命令对象,并将命令对象发送给接收者执行。它可以理解和操作命令对象,但不知道具体的接收者。
  • 命令接收者(Receiver):命令接收者负责具体的执行逻辑,它执行命令对象所定义的操作。
// 命令接收者
class TextEditor {
    private val buffer = StringBuilder()

    fun copy() {

    }

    fun paste() {
        getFromClipBoard().also { add(it) }
    }

    private fun getFromClipBoard(): String {
        return "123"
    }

    fun add(s: String) {
        buffer.append(s)
    }

    fun delete() {
        if (buffer.isNotEmpty()) {
            buffer.deleteCharAt(buffer.lastIndex)
        }
    }

    fun getState(): String {
        return buffer.toString()
    }
}

// 命令接口
interface Command {
    fun execute()
}

class CopyCommand(private val receiver: TextEditor) : Command {
    override fun execute() {
        receiver.copy()
    }

}

class PasteCommand(private val receiver: TextEditor) : Command {
    override fun execute() {
        receiver.paste()
    }

}

fun main() {
    val editor = TextEditor()
    editor.add("Command pattern in text editor.\n")
    // 执行一个CopyCommand:
    // 执行一个CopyCommand:
    val copy: Command = CopyCommand(editor)
    copy.execute()
    editor.add("----\n")
    // 执行一个PasteCommand:
    // 执行一个PasteCommand:
    val paste: Command = PasteCommand(editor)
    paste.execute()
    println(editor.getState())
}

迭代器模式(Iterator Pattern)

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

迭代器模式的核心思想是将容器对象的遍历操作封装到一个迭代器对象中,使得客户端无需关心容器的内部结构,只需要通过迭代器对象来访问容器中的元素。

迭代器模式的主要角色包括:

  • 迭代器接口(Iterator):迭代器接口定义了访问和遍历容器中元素的方法,通常包括获取下一个元素、判断是否还有下一个元素等方法。
  • 具体迭代器类(Concrete Iterator):具体迭代器类实现了迭代器接口,并负责实现迭代器接口中定义的方法。它可以跟踪容器的当前位置,并根据需要进行遍历操作。
  • 容器接口(Container):容器接口定义了获取迭代器对象的方法,用于获取迭代器对象来遍历容器中的元素。
  • 具体容器类(Concrete Container):具体容器类实现了容器接口,并负责实现获取迭代器对象的方法。它可以根据需要创建具体迭代器对象,并将迭代器对象返回给客户端。
// 容器接口 + 具体容器类
class ReverseArrayCollection<T : Any>(vararg objs: T) : Iterable<T> {
    private var array: Array<T> = objs.copyOfRange(0, objs.size) as Array<T>
		
    override fun iterator(): Iterator<T> {
        return ReverseIterator()
    }
	
		// 迭代器接口 + 具体迭代器类
    inner class ReverseIterator : Iterator<T> {
        // 索引位置:
        private var index: Int = this@ReverseArrayCollection.array.size

        override fun hasNext(): Boolean {
            // 如果索引大于0,那么可以移动到下一个元素(倒序往前移动):
            return index > 0
        }

        override fun next(): T {
            // 将索引移动到下一个元素并返回(倒序往前移动):
            index--
            return array[index]
        }
    }
}

fun main(args: Array<String>) {
    for (i in ReverseArrayCollection(1, 2, 3)) {
        println(i)
    }
}

IterableIterator 是 Java 中用于支持迭代操作的两个关键接口,它们的主要区别如下:

  1. 功能不同:

    • Iterable 接口表示一个可迭代的对象,即对象可以在循环中进行遍历。
    • Iterator 接口表示一个迭代器对象,用于遍历和访问可迭代对象中的元素。
  2. 方法不同:

    • Iterable 接口中最重要的方法是 iterator(),用于返回一个迭代器对象。该方法在实现类中被重写,以提供迭代器对象。
    • Iterator 接口中定义了用于遍历和访问元素的方法,如 hasNext()next()remove()
  3. 使用场景不同:

    • Iterable 通常用于表示集合类,如 ListSet 等。它通过实现 Iterable 接口,允许对象能够在增强 for 循环中进行遍历。
    • Iterator 通常用于在迭代过程中访问和管理集合中的元素。它提供了一种一步一步遍历元素的方式,并且可以在迭代过程中进行元素的删除。
  4. 使用方式不同:

    • 对于 Iterable,使用增强 for 循环(也称为 for-each 循环)来遍历集合,它会隐式地调用集合的 iterator() 方法获取迭代器,并一次迭代集合中的每个元素。
    List<String> list = Arrays.asList("A", "B", "C");
    
    for (String element : list) {
        System.out.println(element);
    }
    
    
    • 对于 Iterator,使用显式调用迭代器对象的方法来遍历集合,通常使用 while 循环结合 hasNext()next() 方法来遍历元素。
    List<String> list = Arrays.asList("A", "B", "C");
    
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String element = iterator.next();
        System.out.println(element);
    }
    
    

总之,Iterable 是用于支持集合类在 for-each 循环中进行遍历,而 Iterator 是用于在迭代过程中访问和管理集合中的元素。它们是相辅相成的,迭代器模式的基础之一。

责任链模式(Chain of Responsibility Pattern)

将请求的发送和接收者解耦,使多个对象都有机会处理该请求。

责任链模式的核心思想是将处理请求的对象组成一个链式结构,每个对象都有机会处理请求,但具体由哪个对象处理请求是在运行时确定的。请求沿着链传递,直到被处理或者到达责任链的末尾。

责任链模式的主要角色包括:

  • 抽象处理者(Handler):抽象处理者定义了处理请求的接口,并包含一个指向下一个处理者的引用。它可以决定是否将请求传递给下一个处理者,或者自行处理请求。
  • 具体处理者(Concrete Handler):具体处理者实现了抽象处理者的接口,并负责实际处理请求。如果能够处理请求,则处理请求;否则将请求传递给下一个处理者。
  • 客户端(Client):客户端通过创建并将请求传递给第一个处理者来启动责任链。
interface Handler {
    val name: String
    fun process(request: Request): Boolean?
}

class ManagerHandler(override val name: String, private val amount: Int) : Handler {
    override fun process(request: Request): Boolean? {
        if (request.amount > this.amount) {
            return null
        }
        return request.name != "bob"
    }

}

class HandlerChain {
    private val handlers = ArrayList<Handler>()

    fun addHandler(handler: Handler): HandlerChain {
        handlers.add(handler)
        return this
    }

    fun process(request: Request): Boolean? {
        handlers.forEach { handler ->
            handler.process(request)?.let {
                println("$request ${if (it) "Approved by" else "Denied by"} ${handler.name}")
                return it
            }
        }
        return null
    }
}

fun main(args: Array<String>) {
    HandlerChain()
        .addHandler(ManagerHandler(name = "Manager1", amount = 500))
        .addHandler(ManagerHandler(name = "Manager2", amount = 5000))
        .process(Request(name = "John", amount = 1000))
}

解释器模式(Interpreter Pattern)

定义一个语言的文法,并建立一个解释器来解释该语言中的语句。

解释器模式的核心思想是将一个语言的文法表示成一个抽象语法树(AST),并定义一种解释器来遍历并解释该语法树。解释器模式适用于处理复杂的语法规则和特定领域的问题,可以将问题的定义和求解解耦,提供一种灵活、可扩展的方案。

解释器模式的主要角色包括:

  • 抽象表达式(Abstract Expression):抽象表达式定义了一个解释器的接口,声明了解释操作的方法,通常包括一个或多个解释方法。
  • 终结符表达式(Terminal Expression):终结符表达式是抽象表达式的子类,它实现了解释方法,用于解决终结符的解释操作,不能再向下进行解释。
  • 非终结符表达式(Non-terminal Expression):非终结符表达式是抽象表达式的子类,它实现了解释方法,用于解决非终结符的解释操作,通常由多个终结符和非终结符组成。
  • 上下文环境(Context):上下文环境包含了需要解释的语句和解释器需要的一些全局信息,它提供了解释器操作的上下文环境。
  • 客户端(Client):客户端创建并配置解释器,构建抽象语法树,并将需要解释的语句传递给解释器进行解释。
// 上下文环境
class Context(private val expression: String) {
    fun evaluate(): Expression {
        // 将表达式解析成抽象语法树
        val tokens = expression.split(" ")
        val stack = mutableListOf<Expression>()

        for (token in tokens) {
            if (isOperator(token)) {
                val right = stack.removeAt(stack.lastIndex)
                val left = stack.removeAt(stack.lastIndex)

                val operator = when (token) {
                    "+" -> AddExpression(left, right)
                    else -> throw IllegalArgumentException("Unsupported operator")
                }
                stack.add(operator)
            } else {
                val operand = NumberExpression(token.toInt())
                stack.add(operand)
            }
        }
        return stack.single()
    }

    private fun isOperator(token: String): Boolean {
        return token == "+"
    }
}

// 抽象表达式
interface Expression {
    fun interpret(): Int
}

// 终结符表达式: 数字表达式
class NumberExpression(private val number: Int) : Expression {
    override fun interpret(): Int {
        return number
    }
}

class AddExpression(private val left: Expression, private val right: Expression) : Expression {
    override fun interpret(): Int {
        return left.interpret() + right.interpret()
    }
}

fun main(args: Array<String>) {
    val expression = "1 2 + 3 4 + +"

    val context = Context(expression)
    val e = context.evaluate()

    val result = e.interpret()
    println("结果:$result")
}

中介者模式(Mediator Pattern)

用一个中介对象来封装一系列对象之间的交互,使对象之间的耦合松散,可以独立地改变它们之间的交互。

在中介者模式中,多个对象之间不再直接相互引用和调用,而是通过中介者进行通信。中介者对象担当了协调对象之间交互的角色,它知道各个对象的状态和行为,负责协调它们的交互过程。

中介者模式的核心思想是将对象之间的通信集中管理,从而避免了复杂的依赖关系和混乱的交互。通过将对象间复杂的交互逻辑转移至中介者对象,每个对象只需要关注自身的行为,而不需要了解其他对象的具体实现细节。

中介者模式适用于对象之间存在复杂的交互关系,并且对象之间的依赖关系难以维护的场景。它可以减少对象间的耦合性,使系统更加灵活,并且能够方便地添加新的对象和通信规则。

以下是中介者模式的一些关键角色:

  • 抽象中介者(Mediator):定义了对象之间的通信接口,通常包含多个抽象方法来处理不同类型的交互。
  • 具体中介者(ConcreteMediator):实现了抽象中介者接口,负责协调各个具体对象之间的通信。
  • 抽象同事类(Colleague):定义了对象的基本行为,包含一个指向中介者对象的引用,用于与中介者交互。
  • 具体同事类(ConcreteColleague):实现了抽象同事类的接口,通过中介者对象来与其他同事类进行通信。

中介者模式的使用场景包括:多个对象之间存在复杂的交互逻辑、对象之间的通信会导致紧耦合关系、难以理解和维护对象之间的关系等。

// 抽象中介者
interface Mediator {
    fun sendMessage(message: String, colleague: Colleague)
}

class MediatorImpl : Mediator {

    private val colleagues: MutableList<Colleague> = mutableListOf()

    fun add(colleague: Colleague) {
        colleagues.add(colleague)
    }

    override fun sendMessage(message: String, colleague: Colleague) {
        for (c in colleagues) {
            if (c != colleague) {
                c.receiveMessage(message)
            }
        }
    }

}

// 抽象同事类
abstract class Colleague {
    abstract fun receiveMessage(message: String)
    abstract fun sendMessage(message: String)
}

// 具体同事类
class ColleagueImpl(private val name: String, private val mediator: Mediator) : Colleague() {

    init {
        (mediator as? MediatorImpl)?.add(this)
    }

    override fun receiveMessage(message: String) {
        println("$name 收到消息:$message")
    }

    override fun sendMessage(message: String) {
        mediator.sendMessage(message, this)
    }

}

fun main(args: Array<String>) {
    val mediator = MediatorImpl()

    val colleagueA = ColleagueImpl("ColleagueA", mediator)
    val colleagueB = ColleagueImpl("ColleagueB", mediator)
    val colleagueC = ColleagueImpl("ColleagueC", mediator)

    colleagueA.sendMessage("Hello, everyone!")
    colleagueB.sendMessage("Nice to meet you!")
}

备忘录模式(Memento Pattern)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。

备忘录模式涉及三个主要角色:

  1. 发起人(Originator):发起人是需要被保存状态的对象。它拥有当前状态,并能创建备忘录对象以保存其状态或将状态恢复到先前的状态。
  2. 备忘录(Memento):备忘录对象用于存储发起人的内部状态。它可以包含发起人的状态信息的副本,同时也提供了一种方法来恢复发起人的状态。
  3. 管理者(Caretaker):管理者负责保存和恢复备忘录。它可以存储和管理多个备忘录对象,但不会对备忘录的状态进行修改。

备忘录模式的基本流程如下:

  1. 发起人创建一个备忘录对象,并将其内部状态保存到备忘录中。
  2. 发起人可以使用备忘录来保存当前状态,也可以使用备忘录来将状态恢复到之前的某个状态。
  3. 在需要时,管理者可以请求发起人提供一个备忘录,以便保存当前状态。管理者还可以要求发起人使用某个备忘录来恢复状态。
  4. 发起人根据管理者提供的备忘录,恢复到相应的状态。

备忘录模式的优点包括:

  • 发起人和备忘录之间的耦合度低,发起人不需要知道备忘录的内部结构。
  • 备忘录模式支持撤销和恢复操作。发起人可以随时保存当前状态,并在需要时进行状态恢复。
  • 备忘录模式封装了状态的保存细节,对外部用户提供了简单的接口。

备忘录模式适用于以下情况:

  • 当需要保存和恢复对象的内部状态时,而且直接访问对象的状态会导致封装性问题时,可以考虑使用备忘录模式。
  • 当需要提供撤销和恢复操作时,备忘录模式可以很好地支持这种需求。
  • 当希望封装由于保存状态而产生的依赖关系时,备忘录模式可以提高代码的灵活性和可维护性。
// 备忘录类
class Memento(private val state: String) {
    fun getState(): String {
        return state
    }
}

// 发起人类
class TextEditor(state: String) {
    private val buffer = StringBuilder()

    init {
        setState(state)
    }

    fun add(ch: Char) {
        buffer.append(ch)
    }

    fun add(s: String?) {
        buffer.append(s)
    }

    fun setState(state: String) {
        buffer.delete(0, buffer.length)
        buffer.append(state)
    }

    fun delete() {
        if (buffer.isNotEmpty()) {
            buffer.deleteCharAt(buffer.length - 1)
        }
    }

    fun save(): Memento {
        return Memento(buffer.toString())
    }

    fun restore(memento: Memento) {
        setState(memento.getState())
    }

    fun getState(): String {
        return buffer.toString()
    }
}

// 管理者类
class Caretaker {
    private val mementoList: MutableList<Memento> = mutableListOf()

    fun addMemento(memento: Memento) {
        mementoList.add(memento)
    }

    fun getMemento(index: Int): Memento {
        return mementoList[index]
    }
}

fun main(args: Array<String>) {
    val caretaker = Caretaker()
    val textEditor = TextEditor("I love the world!")
    println(textEditor.getState())

    caretaker.addMemento(textEditor.save())
    textEditor.setState("I hate the world!")

    println(textEditor.getState())

    textEditor.restore(caretaker.getMemento(0))
    println(textEditor.getState())
}

访问者模式(Visitor Pattern)

在不修改现有类的情况下,定义作用于一组对象结构的新操作。

访问者模式的核心思想是将操作(访问者)从对象结构中分离出来,以便可以在不更改对象本身的情况下定义新的操作。通过这种方式,访问者模式实现了开闭原则,使得新增操作变得更加容易。

访问者模式涉及下面的主要角色:

  1. 访问者(Visitor):访问者是一个接口或抽象类,定义了可以对元素对象执行的各种操作。对于对象结构中的每个具体元素,访问者都需要提供相应的操作方法。
  2. 元素(Element):元素是对象结构中的对象,它定义了接受访问者操作的接口。元素可以是单个对象,也可以是对象的集合。
  3. 具体访问者(Concrete Visitor):具体访问者是实现了访问者接口的具体类,它实现了对元素对象的具体操作。
  4. 具体元素(Concrete Element):具体元素是实现了元素接口的具体类,它定义了接受访问者操作的具体实现。
  5. 对象结构(Object Structure):对象结构是一个容器,它包含一组元素对象,并提供了遍历元素的方法。

访问者模式的基本流程如下:

  1. 定义访问者接口(或抽象类),在其中声明针对每个具体元素的操作方法。
  2. 定义元素接口(或抽象类),在其中声明一个接受访问者操作的方法。
  3. 实现具体访问者类,具体访问者类实现了访问者接口,并为每个具体元素提供具体的操作实现。
  4. 实现具体元素类,具体元素类实现了元素接口,并为接受访问者操作的方法提供具体实现。
  5. 定义对象结构,并在其中维护一组元素对象。
  6. 对象结构提供一个遍历元素的方法,在遍历过程中调用每个元素的接受访问者操作的方法,并将具体访问者对象传递给元素。
  7. 客户端代码中,创建具体访问者对象和具体元素对象,并将后者添加到对象结构中。然后,通过调用对象结构的遍历方法,让访问者访问每个元素。
// 访问者接口
interface Visitor {
    fun visitDir(dir: File)
    fun visitFile(file: File)
}

// 对象结构
class FileStructure(private val path: File) {
    fun handle(visitor: Visitor) {
        scan(path, visitor)
    }

    private fun scan(file: File, visitor: Visitor) {
        if (file.isDirectory) {
            // 让访问者处理文件夹:
            visitor.visitDir(file)
            for (sub in file.listFiles()!!) {
                // 递归处理子文件夹:
                scan(sub, visitor)
            }
        } else if (file.isFile) {
            // 让访问者处理文件:
            visitor.visitFile(file)
        }
    }
}

// 具体访问者
class JavaFileVisitor : Visitor {
    override fun visitDir(dir: File) {
        println("Visit dir: $dir")
    }

    override fun visitFile(file: File) {
        if (file.name.endsWith(".java")) {
            println("Found java file: $file")
        }
    }
}

// 具体访问者
class ClassFileCleanerVisitor : Visitor {
    override fun visitDir(dir: File) {}
    override fun visitFile(file: File) {
        if (file.name.endsWith(".class")) {
            println("Will clean class file: $file")
        }
    }
}

fun main() {
    val fs = FileStructure(File("."))
    fs.handle(JavaFileVisitor())
}

访问者模式的优点包括:

  • 可以在不修改元素类的情况下定义新的操作和行为。
  • 访问者模式将相关操作集中在访问者中,使元素类保持简洁。
  • 通过添加新的访问者,可以很容易地对元素进行新的操作扩展。

访问者模式适用于以下情况:

  • 当需要为对象结构中的元素定义新的操作,并且不希望修改元素类的代码时,可以考虑使用访问者模式。
  • 当存在多个不同的操作需要应用于对象结构中的元素时,可以使用访问者模式将这些操作统一抽象出来,避免代码的重复。
class MyFileVisitor : SimpleFileVisitor<Path>() {
    override fun preVisitDirectory(dir: Path?, attrs: BasicFileAttributes?): FileVisitResult {
        println("pre visit dir: $dir")
        return FileVisitResult.CONTINUE
    }

    override fun visitFile(file: Path?, attrs: BasicFileAttributes?): FileVisitResult {
        println("visit file: $file");
        // 返回CONTINUE表示继续访问:
        return FileVisitResult.CONTINUE
    }

}

fun main() {
   Files.walkFileTree(Paths.get("."), MyFileVisitor())
}

模版方法(Template Method)

它定义了一个算法的骨架,将算法中的一些步骤推迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

模板方法的核心思想是抽象出算法的骨架,并将一些步骤的具体实现留给子类来完成。这样,模板方法可以在父类中定义通用的算法结构和步骤顺序,而具体的实现细节则由子类来决定。通过这种方式,模板方法模式使得算法的骨架固定,但具体实现可以多样化。

模板方法涉及下面的主要角色:

  1. 抽象类(Abstract Class):抽象类是包含模板方法的类,它定义了算法的骨架和步骤顺序。抽象类可以包含抽象方法、具体方法和钩子方法。抽象方法由子类来实现,具体方法在抽象类中有默认实现,而钩子方法是一个具体方法,子类可以选择是否对其进行重写。
  2. 具体类(Concrete Class):具体类是抽象类的子类,它实现了抽象类中定义的抽象方法,并提供了具体的实现。
  3. 钩子方法(Hook Method):钩子方法是在抽象类中定义的具体方法,它可以由子类选择性地重写,以影响算法的行为。

模板方法的基本流程如下:

  1. 在抽象类中定义一个模板方法,该方法定义了算法的骨架和步骤顺序,可能包含抽象方法、具体方法和钩子方法。
  2. 具体类继承抽象类,并实现抽象方法,提供具体的实现。
  3. 钩子方法可选地由具体类重写,以影响算法的行为。
  4. 客户端代码直接使用具体类。
// 抽象类
abstract class AbstractSetting {
    fun getSetting(key: String?): String? {
        var value = lookupCache(key)
        if (value == null) {
            value = readFromDatabase(key)
            putIntoCache(key, value)
        }
        return value
    }
		
		// 钩子方法
	  fun readFromDatabase(key: String?): String {
        return "Hello World!"
    }
		
		// 抽象方法
    protected abstract fun lookupCache(key: String?): String?
    protected abstract fun putIntoCache(key: String?, value: String?)
}

class LocalSetting : AbstractSetting() {
    private val cache: MutableMap<String?, String?> = HashMap()
    override fun lookupCache(key: String?): String? {
        return cache[key]
    }

    override fun putIntoCache(key: String?, value: String?) {
        cache[key] = value
    }
}

模板方法的优点包括:

  • 可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
  • 将算法的通用逻辑和变化的实现细节分离,提高了代码的复用性和可维护性。
  • 可以通过继承和重写来实现行为的扩展。

模板方法适用于以下情况:

  • 当存在一组相似的算法,并且它们之间的算法结构相同,但具体的实现细节不同的时候,可以考虑使用模板方法。
  • 当不希望子类改变算法的骨架,只允许改变其中的某些具体实现细节时,也可以使用模板方法。

源码

design_method

  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值