摘要

本文介绍了设计模式中的六大基本原则:单一职责原则、开放封闭原则、子类替换父类原则、接口调用原则、类对象交互原则和接口隔离原则。通过具体代码示例,阐述了每个原则的核心思想和应用场景,旨在提高代码的可维护性和灵活性。

1. 单一职责原则

1.1. 核心思想

一个类应该有且仅有一个原因引起它的变更。说人话:一个类只负责一项功能或一类相似的功能。

这句话这样说可能不太容易理解,解释一下:类 T 负责两个不同的职责(可以理解为功能):职责 P1、职责 P2。当由于职责 P1 需求发生改变而需要修改类 T 时,有可能会导致原本运行正常的职责 P2 功能发生故障。这就不符合单一职责原则,这时就应该将类 T 拆分成两个类 T1、T2,使 T1 完成职责 P1 功能,T2 完成职责 P2 功能。这样,当修改类 T1 时,不会使职责 P2 发生故障风险;同理,当修改 T2 时,也不会使职责 P1 发生故障风险。

当然这个“一”并不是绝对的,应该理解为一个类只负责尽可能独立的一项功能,尽可能少的职责。就好比一个人,我们的精力、时间都是有限的;如果我们什么事情都做,那什么事情都做不好;而应该集中精力做一件事,才能把事情做好。

1.2. 不遵守单一职责的例子

以下是一个没有遵循单一职责原则的例子:

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public void calculatePay() {
        System.out.println("Calculating pay for " + name);
    }

    public void saveToDatabase() {
        System.out.println("Saving " + name + " to database");
    }
}

问题:

  1. Employee 类负责两件事情:计算薪资和保存员工信息到数据库。
  2. 如果薪资计算规则改变或者数据库保存逻辑改变,都需要修改 Employee 类,这就导致类的职责过于复杂,增加了维护的难度。

1.3. 遵守单一职责的改进

将职责拆分为多个类,每个类只负责一个职责:

// 员工类,只负责保存员工信息
class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }
}

// 负责薪资计算的类
class SalaryCalculator {
    public void calculatePay(Employee employee) {
        System.out.println("Calculating pay for " + employee.getName());
    }
}

// 负责数据持久化的类
class EmployeeRepository {
    public void saveToDatabase(Employee employee) {
        System.out.println("Saving " + employee.getName() + " to database");
    }
}

在使用时,将职责分开:

public class Main {
    public static void main(String[] args) {
        Employee employee = new Employee("John", 5000);

        SalaryCalculator calculator = new SalaryCalculator();
        calculator.calculatePay(employee);

        EmployeeRepository repository = new EmployeeRepository();
        repository.saveToDatabase(employee);
    }
}

2. 开放封闭原则

2.1. 核心思想

软件实体(如类、模块、函数等)应该对拓展开放,对修改关闭。

说人话:在一个软件产品的生命周期内,不可避免的会有一些业务和需求变化。我们在设计代码的时候应该尽可能地考虑这些变化,在增加一个功能时,应当尽可能地不去改动已有的代码;当修改一个模块时不应该影响到其他的模块。

2.2. 开放封闭原则例子

在一个软件产品的生命周期内,不可避免的会有一些业务和需求变化。我们在设计代码的时候应该尽可能地考虑这些变化,在增加一个功能时,应当尽可能地不去改动已有的代码;当修改一个模块时不应该影响到其他的模块。

package com.zhuangxiaoyan.hyxftest.designpattern;

import java.util.ArrayList;
import java.util.List;

/**
 * Main
 *
 * @author xjl
 * @version 2024/12/01 19:48
 **/

// 抽象类 Animal
abstract class Animal implements AnimalMoving {

    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public abstract void moving();
}


interface AnimalMoving{

    void moving();
}

// 陆生动物类 TerrestrialAnimal
class TerrestrialAnimal extends Animal {
    public TerrestrialAnimal(String name) {
        super(name);
    }

    @Override
    public void moving() {
        System.out.println(name + " 在陆上跑...");
    }

    public void checkFood(Food food) {
        // 假设 food 的类别判断逻辑
        if (food.getCategory().equals("植物")) {
            System.out.println(name + " 可以吃这个食物");
        } else {
            System.out.println(name + " 不喜欢这个食物");
        }
    }
}

// 水生动物类 AquaticAnimal
class AquaticAnimal extends Animal {
    public AquaticAnimal(String name) {
        super(name);
    }

    @Override
    public void moving() {
        System.out.println(name + " 在水里游...");
    }
}

// 鸟类动物类 BirdAnimal
class BirdAnimal extends Animal {
    public BirdAnimal(String name) {
        super(name);
    }

    @Override
    public void moving() {
        System.out.println(name + " 在天空飞...");
    }
}

// 猴子类 Monkey 继承自 TerrestrialAnimal
class Monkey extends TerrestrialAnimal {
    public Monkey(String name) {
        super(name);
    }

    public void climbing() {
        System.out.println(name + " 在爬树,动作灵活轻盈...");
    }
}

class BigMonkey extends Monkey{

    public BigMonkey(String name) {
        super(name);
    }

    public void moving() {
        System.out.println(name + "跳舞...");
    }
    public void climbing() {
        System.out.println(name + "跳跳跳跳...");
    }
}


// 食物类,用于示例
class Food {
    private String category;

    public Food(String category) {
        this.category = category;
    }

    public String getCategory() {
        return category;
    }
}

// 动物园类 Zoo
class Zoo {
    private List<Animal> animals = new ArrayList();

    public void addAnimal(Animal animal) {
        animals.add(animal);
    }

    public void displayActivity() {
        System.out.println("观察每一种动物的活动方式:");
        for (Animal animal : animals) {
            animal.moving();
        }
    }
}

// 主类测试
public class Main {
    public static void main(String[] args) {
        Zoo zoo = new Zoo();

        // 创建动物
        Animal tiger = new TerrestrialAnimal("老虎");
        Animal fish = new AquaticAnimal("金鱼");
        Animal eagle = new BirdAnimal("老鹰");
        Monkey monkey = new Monkey("猴子");
        BigMonkey bigMonkey = new BigMonkey("大猩猩");

        // 添加动物到动物园
        zoo.addAnimal(tiger);
        zoo.addAnimal(fish);
        zoo.addAnimal(eagle);
        zoo.addAnimal(monkey);
        zoo.addAnimal(bigMonkey);

        // 显示动物活动
        zoo.displayActivity();

        // 检查猴子是否能吃食物
        Food food = new Food("植物");
        monkey.checkFood(food);
        monkey.climbing();
    }
}

3. 子类替换父类原则

3.1. 核心思想:

所有能引用基类的地方必须能透明地使用其子类的对象。有一个类 T 有两个子类 T1、T2,能够使用 T 的对象的地方,就能用 T1 的对象或 T2 的对象,因为子类拥有父类的所有属性和行为。

说人话:只要父类能出现的地方子类就能出现(就可以用子类来替换他),反之,子类能出现的地方父类就不一定能出现(子类拥有父类的所有属性和行为,但子类拓展了更多的功能)。

3.2. 子类替换父类原则示例

package com.zhuangxiaoyan.hyxftest.designpattern;

import java.util.ArrayList;
import java.util.List;

/**
 * Main
 *
 * @author xjl
 * @version 2024/12/01 19:48
 **/

// 抽象类 Animal
abstract class Animal implements AnimalMoving {

    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public abstract void moving();
}


interface AnimalMoving{

    void moving();
}

// 陆生动物类 TerrestrialAnimal
class TerrestrialAnimal extends Animal {
    public TerrestrialAnimal(String name) {
        super(name);
    }

    @Override
    public void moving() {
        System.out.println(name + " 在陆上跑...");
    }

    public void checkFood(Food food) {
        // 假设 food 的类别判断逻辑
        if (food.getCategory().equals("植物")) {
            System.out.println(name + " 可以吃这个食物");
        } else {
            System.out.println(name + " 不喜欢这个食物");
        }
    }
}

// 水生动物类 AquaticAnimal
class AquaticAnimal extends Animal {
    public AquaticAnimal(String name) {
        super(name);
    }

    @Override
    public void moving() {
        System.out.println(name + " 在水里游...");
    }
}

// 鸟类动物类 BirdAnimal
class BirdAnimal extends Animal {
    public BirdAnimal(String name) {
        super(name);
    }

    @Override
    public void moving() {
        System.out.println(name + " 在天空飞...");
    }
}

// 猴子类 Monkey 继承自 TerrestrialAnimal
class Monkey extends TerrestrialAnimal {
    public Monkey(String name) {
        super(name);
    }

    public void climbing() {
        System.out.println(name + " 在爬树,动作灵活轻盈...");
    }
}

class BigMonkey extends Monkey{

    public BigMonkey(String name) {
        super(name);
    }

    public void moving() {
        System.out.println(name + "跳舞...");
    }
    public void climbing() {
        System.out.println(name + "跳跳跳跳跳跳跳跳跳跳跳跳...");
    }
}


// 食物类,用于示例
class Food {
    private String category;

    public Food(String category) {
        this.category = category;
    }

    public String getCategory() {
        return category;
    }
}

// 动物园类 Zoo
class Zoo {
    private List<Animal> animals = new ArrayList();

    public void addAnimal(Animal animal) {
        animals.add(animal);
    }

    public void displayActivity() {
        System.out.println("观察每一种动物的活动方式:");
        for (Animal animal : animals) {
            animal.moving();
        }
    }

    public void monkeyClimbing(Monkey monkey) {
        monkey.climbing();
    }
}

// 主类测试
public class Main {
    public static void main(String[] args) {
        Zoo zoo = new Zoo();

        // 创建动物
        Animal tiger = new TerrestrialAnimal("老虎");
        Animal fish = new AquaticAnimal("金鱼");
        Animal eagle = new BirdAnimal("老鹰");
        Monkey monkey = new Monkey("猴子");
        BigMonkey bigMonkey = new BigMonkey("大猩猩");

        // 添加动物到动物园
        zoo.addAnimal(tiger);
        zoo.addAnimal(fish);
        zoo.addAnimal(eagle);
        zoo.addAnimal(monkey);
        zoo.addAnimal(bigMonkey);

        // 显示动物活动
        zoo.displayActivity();

        // 检查猴子是否能吃食物
        Food food = new Food("植物");
        monkey.checkFood(food);
        monkey.climbing();
        // 测试猴子
        zoo.monkeyClimbing(monkey);
        // 测试大猩猩
        zoo.monkeyClimbing(bigMonkey);

    }
}

4. 接口调用原则

4.1. 核心思想

高层模块就是调用端,低层模块就是具体实现类,抽象就是指接口或抽象类,细节也是指具体的实现类,也就是说我们只依赖抽象编程。

说人话:把具有相同特征或相似功能的类,抽象成接口或抽象类,让具体的实现类继承这个抽象类(或实现对应的接口)。抽象类(接口)负责定义统一的方法,实现类负责实现具体功能的实现

4.2. 接口调用原则示例

// 定义抽象类 Animal
abstract class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    // 吃食物的方法
    public void eat(Food food) {
        if (checkFood(food)) {
            System.out.println(name + "进食" + food.getName());
        } else {
            System.out.println(name + "不吃" + food.getName());
        }
    }

    // 抽象方法:检查是否能吃某种食物
    protected abstract boolean checkFood(Food food);
}

// 狗类,继承 Animal
class Dog extends Animal {
    public Dog() {
        super("狗");
    }

    @Override
    protected boolean checkFood(Food food) {
        return "肉类".equals(food.category());
    }
}

// 燕子类,继承 Animal
class Swallow extends Animal {
    public Swallow() {
        super("燕子");
    }

    @Override
    protected boolean checkFood(Food food) {
        return "昆虫".equals(food.category());
    }
}

// 定义抽象类 Food
abstract class Food {
    protected String name;

    public Food(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    // 抽象方法:返回食物的类别
    public abstract String category();

    // 抽象方法:返回食物的营养成分
    public abstract String nutrient();
}

// 肉类食物,继承 Food
class Meat extends Food {
    public Meat() {
        super("肉");
    }

    @Override
    public String category() {
        return "肉类";
    }

    @Override
    public String nutrient() {
        return "蛋白质、脂肪";
    }
}

// 虫子类食物,继承 Food
class Worm extends Food {
    public Worm() {
        super("虫子");
    }

    @Override
    public String category() {
        return "昆虫";
    }

    @Override
    public String nutrient() {
        return "蛋白质、微量元素";
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal swallow = new Swallow();

        Food meat = new Meat();
        Food worm = new Worm();

        // 狗吃肉
        dog.eat(meat); // 输出:狗进食肉
        // 狗吃虫子
        dog.eat(worm); // 输出:狗不吃虫子

        // 燕子吃肉
        swallow.eat(meat); // 输出:燕子不吃肉
        // 燕子吃虫子
        swallow.eat(worm); // 输出:燕子进食虫子
    }
}

在上面的例子中,动物抽象出一个父类 Animal,食物也抽象出一个抽象类 Food。Animal 抽象来不依赖于细节(具体的食物类),具体的动物(如 Dog),也不依赖于细节(具体的食物类)。

5. 类对象交互原则

5.1. 核心思想:

如类 A 中有类B的对象;类 B 中有类 C 的对象,调用方有一个类 A 的对象 a,这时要访问 C 对象的属性,不要采用类似下面的写法:

a.getB().getC().getProperties()

而应该是:

a.getCProperties()

至于 getCProperties 怎么实现是类 A 要负责的事情,我只和我直接的对象 a 进行交互,不访问我不了解的对象。

说人话:一个类对自己依赖的类知道的越少越好,只需要和直接的对象进行交互,而不用在乎这个对象的内部组成结构。

大家都知道大熊猫是我们国家的国宝,为数不多的熊猫大部分都存活在动物动物园中。而动物园内种类繁多,布局复杂,如有鸟类馆、熊猫馆。假设某国外领导人来访华,参观我们的动物园,他想知道动物园内叫“贝贝”大熊猫年龄多大,体重多少。他难道要先去调取熊猫馆的信息,再去查找叫“贝贝”的这只大熊猫,再去看他的信息吗?显然不用,他只要问一下动物园的馆长就可以了。动物园的馆长会告诉他所有需要的信息,因为他只认识动物园的馆长,并不了解动物园的内部结构,也不需要去了解。

5.2. 类对象交互原则示例

zooAdmin.getPandaBeiBeiInfo()

6. 接口隔离原则

6.1. 核心思想

类 A 通过接口 interface 依赖类 C,类 B 通过接口 interface 依赖类 D,如果接口 interface 对于类 A 和类 B 来说不是最小接口,则类 C 和类 D 必须去实现他们不需要的方法。

说人话:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

接口尽量小,但是要有限度 。当发现一个接口过于臃肿时,就要对这个接口进行适当的拆分,但是如果过小,则会造成接口数量过多,使设计复杂化;所以一定要适度。

6.2. 接口隔离原则示例

我们知道在生物分类学中,从高到低有界、门(含亚门)、纲、目、科、属、种七个等级的分类。脊椎动物就是脊索动物的一个亚门,是万千动物世界中数量最多、结构最复杂的一个门类。哺乳动物(也称兽类)、鸟类、鱼类是脊椎动物中最重要的三个子分类;哺乳动物大都生活于陆地,鱼类都生活在水里,而鸟类大都能飞行。

但这些特性并不是绝对的,如蝙蝠是哺乳动物,但它却能飞行;鲸鱼也是哺乳动物,却生活在海中;天鹅是鸟类,能在天上飞,也能在水里游,还能在地上走。所以我们上面的示例中将动物根据活动场所分为水生动物、陆生动物和飞行动物是不够准确的,因为奔跑、游泳、飞翔是动物的一种行为,应该抽象成接口,而且有些动物可能同时具有多种行为。 我们应该根据生理特征来分类,如哺乳类、鸟类、鱼类;哺乳类动物具有恒温,胎生,哺乳等生理特征;鸟类动物具有恒温,卵生,前肢成翅等生理特征;鱼类动物具有流线型体形,用鳃呼吸等生理特征。

这里将奔跑、游泳、飞翔抽象成接口就是对接口的一种细粒度拆分,提高程序设计灵活性。代码的实现如下:

// 定义抽象类 Animal
abstract class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    // 生理特征
    public abstract void feature();

    // 活动方式
    public abstract void moving();
}

// 定义接口 IRunnable:奔跑行为
interface IRunnable {
    void running();
}

// 定义接口 IFlyable:飞行行为
interface IFlyable {
    void flying();
}

// 定义接口 INatatory:游泳行为
interface INatatory {
    void swimming();
}

// 哺乳动物类,继承 Animal,实现奔跑接口 IRunnable
class MammalAnimal extends Animal implements IRunnable {
    public MammalAnimal(String name) {
        super(name);
    }

    @Override
    public void feature() {
        System.out.println(name + "的生理特征:恒温,胎生,哺乳。");
    }

    @Override
    public void running() {
        System.out.println("在陆上跑...");
    }

    @Override
    public void moving() {
        System.out.print(name + "的活动方式:");
        running();
    }
}

// 鸟类动物类,继承 Animal,实现飞行接口 IFlyable
class BirdAnimal extends Animal implements IFlyable {
    public BirdAnimal(String name) {
        super(name);
    }

    @Override
    public void feature() {
        System.out.println(name + "的生理特征:恒温,卵生,前肢成翅。");
    }

    @Override
    public void flying() {
        System.out.println("在天空飞...");
    }

    @Override
    public void moving() {
        System.out.print(name + "的活动方式:");
        flying();
    }
}

// 鱼类动物类,继承 Animal,实现游泳接口 INatatory
class FishAnimal extends Animal implements INatatory {
    public FishAnimal(String name) {
        super(name);
    }

    @Override
    public void feature() {
        System.out.println(name + "的生理特征:流线型体形,用鳃呼吸。");
    }

    @Override
    public void swimming() {
        System.out.println("在水里游...");
    }

    @Override
    public void moving() {
        System.out.print(name + "的活动方式:");
        swimming();
    }
}

// 蝙蝠类,继承 MammalAnimal,同时实现飞行接口 IFlyable
class Bat extends MammalAnimal implements IFlyable {
    public Bat(String name) {
        super(name);
    }

    @Override
    public void running() {
        System.out.println("行走功能已经退化。");
    }

    @Override
    public void flying() {
        System.out.println("在天空飞...");
    }

    @Override
    public void moving() {
        System.out.print(name + "的活动方式:");
        flying();
        running();
    }
}

// 天鹅类,继承 BirdAnimal,同时实现奔跑接口 IRunnable 和游泳接口 INatatory
class Swan extends BirdAnimal implements IRunnable, INatatory {
    public Swan(String name) {
        super(name);
    }

    @Override
    public void running() {
        System.out.print("在陆上跑...");
    }

    @Override
    public void swimming() {
        System.out.print("在水里游...");
    }

    @Override
    public void moving() {
        System.out.print(name + "的活动方式:");
        running();
        System.out.print(" ");
        swimming();
        System.out.print(" ");
        flying();
    }
}

// 鲫鱼类,继承 FishAnimal
class CrucianCarp extends FishAnimal {
    public CrucianCarp(String name) {
        super(name);
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        // 哺乳动物
        MammalAnimal mammal = new MammalAnimal("老虎");
        mammal.feature();
        mammal.moving();

        System.out.println();

        // 鸟类动物
        BirdAnimal bird = new BirdAnimal("麻雀");
        bird.feature();
        bird.moving();

        System.out.println();

        // 鱼类动物
        FishAnimal fish = new FishAnimal("鲤鱼");
        fish.feature();
        fish.moving();

        System.out.println();

        // 蝙蝠
        Bat bat = new Bat("蝙蝠");
        bat.feature();
        bat.moving();

        System.out.println();

        // 天鹅
        Swan swan = new Swan("天鹅");
        swan.feature();
        swan.moving();

        System.out.println();

        // 鲫鱼
        CrucianCarp crucianCarp = new CrucianCarp("鲫鱼");
        crucianCarp.feature();
        crucianCarp.moving();
    }
}

接口的抽象行为:

  • IRunnable 表示奔跑行为。
  • IFlyable 表示飞行行为。
  • INatatory 表示游泳行为。

接口解耦动物的分类与其行为,实现灵活组合。

类的生理分类:

  • 动物分类基于生理特征,MammalAnimalBirdAnimalFishAnimal 是具体的分类。

多行为的灵活实现:

  • 天鹅实现了多个行为接口(IRunnableINatatory),支持多种行为组合。
  • 蝙蝠继承 MammalAnimal 并实现 IFlyable,体现其特殊性。

抽象与多态:

  • 通过抽象类和接口,方法调用使用多态实现(如 moving() 方法根据子类行为动态调用)。

博文参考

《软件设计模式》

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐