掘金 后端 ( ) • 2024-04-07 10:18

设计模式学习(三)——《大话设计模式》

重构

主要目的

  • 改进软件设计:通过重构,可以去除系统中的设计瑕疵(如重复代码、过长函数、过大类等),使得设计更加清晰、简洁。
  • 提高可读性:使代码更容易被他人理解,包括团队成员或未来的维护者。
  • 提高可维护性:通过简化代码结构,降低了修改代码时引入错误的风险,使得未来的维护和扩展更加容易。
  • 优化性能:虽然重构本身不直接以提高性能为目标,但在重构过程中,有时可以识别出并优化掉一些性能瓶颈。

需要遵循的基本原则

  • 不要在添加新功能时进行重构,这样可以避免同时引入新功能和修改现有功能导致的复杂性增加。
  • 在进行重构之前,确保有一套可靠的测试用例。
  • 一次只做一点重构,这样可以降低引入错误的风险,并且使得重构过程更容易管理。

常用的重构手法

  • 提炼函数(Extract Method):将代码中的一段可以独立出来的逻辑提取到一个新的函数中。
  • 合并重复的条件片段(Consolidate Conditional Expression):如果在条件语句的每个分支中有相同的代码,可以将这段代码提取到条件语句外面。
  • 移除魔法数(Replace Magic Number with Symbolic Constant):将代码中的硬编码数字替换为含义明确的常量。
  • 以多态取代条件表达式(Replace Conditional with Polymorphism):对于根据对象类型不同而执行不同行为的情况,可以考虑使用多态来处理。

代码示例

#重构前
public class RefactorExample {
    public void printUserInfo(String name, String email, int age) {
        System.out.println("Name: " + name);
        System.out.println("Email: " + email);
        System.out.println("Age: " + age);
    }
}

#重构后
public class RefactorExample {
    public void printUserInfo(String name, String email, int age) {
        printName(name);
        printEmail(email);
        printAge(age);
    }
    
    private void printName(String name) {
        System.out.println("Name: " + name);
    }
    
    private void printEmail(String email) {
        System.out.println("Email: " + email);
    }
    
    private void printAge(int age) {
        System.out.println("Age: " + age);
    }
}

抽象类

在软件设计模式中,抽象类是一种基础的设计工具,用于定义和封装一组具有共同特征和行为的对象的接口。抽象类通常作为基类(父类)使用,定义一些子类(派生类)都应该拥有的通用方法和属性,但可能不提供这些方法的具体实现。这些具体实现留给抽象类的子类去完成,使得在不同的子类中可以有不同的实现方式。

主要特点

  • 不能被实例化:抽象类不能直接创建对象实例,它的存在主要是为了被继承。
  • 可以包含抽象方法和具体方法:抽象方法是没有具体实现的方法,仅仅是一个声明,具体的实现留给继承它的子类。同时,抽象类中也可以包含具体实现的方法;抽象的方法是必须被子类重写的方法。
  • 强制要求继承:继承抽象类的子类必须实现所有的抽象方法,除非子类也被声明为抽象类。

抽象类与接口的区别

虽然抽象类和接口都可以用来定义抽象层和强制子类实现特定方法,但它们之间存在一些关键区别:

  • 实现方式:一个类只能继承一个抽象类,但可以实现多个接口。
  • 成员类型:抽象类可以包含构造函数、字段、具体方法等,而接口主要定义方法和属性。
  • 访问修饰符:抽象类中的成员可以有不同的访问修饰符(如private, protected, public),而接口中的所有成员默认都是public的。

抽象类在设计模式中的应用

  1. 模板方法模式(Template Method Pattern):在这个模式中,抽象类中定义了执行算法的模板框架。算法的步骤被定义为一系列抽象方法,留给子类去具体实现。这样做可以确保算法的结构保持不变,同时允许子类提供部分步骤的实现。
  2. 工厂方法模式(Factory Method Pattern):在工厂方法模式中,抽象类通常定义一个创建对象的接口,让子类决定实例化哪一个类。抽象类不直接创建对象,而是让其子类定义创建哪个对象。
  3. 策略模式(Strategy Pattern):在策略模式中,一组算法被封装起来,并使它们可以相互替换。这些算法的不同实现可能会作为一个抽象类的子类,这个抽象类定义了所有算法都应遵循的接口。

代码示例

abstract class Animal {
    abstract void makeSound();

    public void eat() {
        System.out.println("This animal eats food.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Bark");
    }
}

接口

在软件设计中,接口是一种规范,定义了一组方法的声明,这些方法代表了某一特定的功能或一组相关的功能。接口规定了实现它的类必须提供接口中声明的所有方法的具体实现。

接口是一种强制约束,确保遵循接口的类具有接口定义的行为;接口是把隐式公共方法和属性组合起来,以封装特定功能的一个集合,一旦类实现了接口,类就可以支持接口所指定的所有属性和成员。声明接口在语法上与抽象类完全相同,但是不允许提供接口中任何成员的执行方式。

接口的主要特点

  • 抽象性:接口中的方法都是抽象的,没有具体的实现。这意味着它仅仅定义了应该做什么,而不是怎么做。
  • 契约:接口作为一种契约,确保实现接口的类遵循一定的行为模式。这对于定义模块或组件之间的交互非常重要。
  • 多继承:在许多编程语言中,类只能从一个类继承,但可以实现多个接口。这提供了一种形式的多继承,允许对象拥有多种类型。

接口在设计模式中的应用

  1. 策略模式(Strategy Pattern):在策略模式中,定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换。这些算法可以实现相同的接口,这样的话,算法可以在运行时互换,而不会影响到使用算法的客户。
  2. 观察者模式(Observer Pattern):在观察者模式中,对象(称为观察者)希望在另一个对象(称为主题)状态改变时收到通知。主题实现注册和注销观察者的接口,而观察者实现一个更新接口,以便在主题状态改变时被通知。
  3. 工厂模式(Factory Pattern):工厂模式中,创建对象的接口允许类将创建过程委托给一个或多个具体子类。这样做可以在不指定具体类的情况下生成对象。

接口和抽象类的区别:

  1. 设计意图:接口主要用于定义行为规范;抽象类用于为一系列子类提供一个共同的、已实现的基础功能。
  2. 使用场景:当多个类之间存在功能上的相似性但没有共同的类层次结构时,更倾向于使用接口。当需要共享代码时,可以使用抽象类。
  3. 功能实现:接口不能包含任何方法的实现,而抽象类可以包含具体方法。
  4. 继承与实现:类可以实现多个接口,但通常只能继承一个抽象类(取决于具体语言的继承规则)。

代码示例

interface Animal {
    void eat();
    void sleep();
}

class Dog implements Animal {
    public void eat() {
        System.out.println("Dog is eating");
    }
    
    public void sleep() {
        System.out.println("Dog is sleeping");
    }
}

public class TestInterface {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.eat();
        myDog.sleep();
    }
}

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def eat(self):
        pass
    
    @abstractmethod
    def sleep(self):
        pass

class Dog(Animal):
    def eat(self):
        print("Dog is eating")
    
    def sleep(self):
        print("Dog is sleeping")

myDog = Dog()
myDog.eat()
myDog.sleep()

using System;

interface IAnimal {
    void Eat();
    void Sleep();
}

class Dog : IAnimal {
    public void Eat() {
        Console.WriteLine("Dog is eating");
    }
    
    public void Sleep() {
        Console.WriteLine("Dog is sleeping");
    }
}

class Program {
    static void Main(string[] args) {
        Dog myDog = new Dog();
        myDog.Eat();
        myDog.Sleep();
    }
}

集合

在软件设计模式的语境中,"集合"(Collection)通常指的是一种数据结构,用于存储和管理对象的群组。虽然“集合”本身不是一种特定的设计模式,它在多种设计模式中扮演着重要的角色,特别是在那些涉及到对象集合管理和操作的模式中。以下是一些使用集合的设计模式和概念:

  1. 迭代器模式(Iterator Pattern) 迭代器模式提供了一种方法,使得可以顺序访问一个聚合对象中各个元素,而又无需暴露该对象的内部表示。在这个模式中,“集合”扮演着聚合对象的角色,而迭代器则用于遍历集合中的元素。这使得客户端可以统一的方式遍历不同的集合类型,如列表、树或图。

  2. 组合模式(Composite Pattern) 组合模式允许客户通过一个统一的接口操作单个对象和对象的组合。在这个模式中,可以将对象组织成树形结构以表示部分-整体的层次结构。这里的“集合”可以看作是树中的节点,既可以代表单个对象,也可以代表一个包含多个对象的复合对象。

  3. 观察者模式(Observer Pattern) 在观察者模式中,一个目标对象管理所有依赖于它的观察者对象,并且在它的状态发生改变时主动通知观察者。这里,目标对象通常会持有一个观察者列表(集合)作为其状态的一部分,以便能够迅速地通知所有观察者。

  4. 策略模式(Strategy Pattern) 策略模式定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换。虽然策略模式本身不直接涉及到集合的管理,但在实际应用中,经常会用到集合来管理和存储可用的策略对象。

  5. 享元模式(Flyweight Pattern) 享元模式用于减少创建对象的数量,以减少内存占用和提高性能。这是通过共享尽可能多的相似对象来实现的。在这个模式中,通常会有一个工厂类,它维护一个“集合”,用来存储已经创建的享元对象。当客户请求一个享元对象时,工厂会检查该对象是否已经存在于集合中,如果存在,则返回现有对象;如果不存在,则创建一个新的享元对象。

在软件设计中,集合和相关操作提供了强大而灵活的方式来管理和操作对象群组。通过将集合与设计模式结合使用,开发者可以创建出更加清晰、可维护和高效的系统。

代码示例

"使用动态数组模拟集合"
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr;
    int size = 10;
    
    // 动态分配内存
    arr = (int*)malloc(size * sizeof(int));
    
    // 填充数据
    for(int i = 0; i < size; i++) {
        arr[i] = i;
    }
    
    // 打印数据
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    
    // 释放内存
    free(arr);
    
    return 0;
}

#使用ArrayList
import java.util.ArrayList;
import java.util.List;

public class CollectionExample {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        
        // 添加元素
        for(int i = 0; i < 10; i++) {
            list.add(i);
        }
        
        // 遍历集合
        for(Integer num : list) {
            System.out.println(num);
        }
    }
}

# 创建列表
my_list = [i for i in range(10)]

# 遍历列表
for item in my_list:
    print(item)