六大设计原则

六大设计原则

六大设计原则,也称为SOLID原则,是一组编程指导原则,它们可以帮助我们编写易于维护和扩展的高质量代码。这些原则是:

  1. 单一职责原则(SRP):

一个类应该只有一个责任,也就是说,它只应该有一个引起它变化的原因。

public class UserManager {
    public void register(User user) {
        // 注册用户
    }
    public void changePassword(User user, String newPassword) {
        // 修改密码
    }
    // 其他用户管理相关方法...
}

public class UserAuthenticator {
    public void login(String username, String password) {
        // 用户登录
    }
    // 其他用户认证相关方法...
}

public class EmailSender {
    public void sendEmail(User user, String subject, String message) {
        // 发送邮件
    }
    // 其他邮件相关方法...
}

public class SMSSender {
    public void sendSMS(User user, String message) {
        // 发送短信
    }
    // 其他短信相关方法...
}

上面的代码实现了用户的注册、登录、修改密码、发送邮件和发送短信等多个功能。这个类的职责不仅包括管理用户,还包括与用户的交互和通信,这违反了 SRP 原则。

为了符合 SRP,我们需要将这个类的职责分解成多个独立的类,每个类都只负责单一职责。例如,我们可以创建以下几个类:

public class UserManager {
    public void register(User user) {
        // 注册用户
    }
    public void changePassword(User user, String newPassword) {
        // 修改密码
    }
    // 其他用户管理相关方法...
}

public class UserAuthenticator {
    public void login(String username, String password) {
        // 用户登录
    }
    // 其他用户认证相关方法...
}

public class EmailSender {
    public void sendEmail(User user, String subject, String message) {
        // 发送邮件
    }
    // 其他邮件相关方法...
}

public class SMSSender {
    public void sendSMS(User user, String message) {
        // 发送短信
    }
    // 其他短信相关方法...
}

现在,每个类都只负责单一职责,代码更加清晰、简洁和易于维护。如果要增加或修改某个功能,只需要修改相应的类,而不会影响其他类的功能。这样,我们就遵循了 SRP 原则,使代码更加健壮和可扩展。

  1. 开闭原则(OCP):

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

public class Circle {
    private double radius;
    public Circle(double radius) {
        this.radius = radius;
    }
    public double getRadius() {
        return radius;
    }
    public double getArea() {
        return Math.PI * radius * radius;
    }
    // 其他方法...
}

上面的代码实现了一个圆形类,包括计算圆的半径和面积等方法。现在,我们要增加一个新的功能,计算圆的周长。

如果直接在 Circle 类中添加一个新的方法,那么就违反了 OCP 原则,因为这样会修改已有代码。为了符合 OCP,我们需要通过扩展来增加新的功能,而不是修改已有代码。

我们可以创建一个新的接口 Shape,其中包括计算面积、周长等方法:

public interface Shape {
    double getArea();
    double getPerimeter();
}

然后,我们可以创建一个新的类 Circle2,实现 Shape 接口并包括计算圆的周长等方法:

public class Circle2 implements Shape {
    private double radius;
    public Circle2(double radius) {
        this.radius = radius;
    }
    public double getRadius() {
        return radius;
    }
    public double getArea() {
        return Math.PI * radius * radius;
    }
    public double getPerimeter() {
        return 2 * Math.PI * radius;
    }
    // 其他方法...
}

这样,我们就可以通过扩展来增加新的功能,而不是修改已有代码。客户端只依赖于 Shape 接口而不依赖于具体实现,这也符合 OCP 的要求。如果我们要增加其他形状的类,只需要实现 Shape 接口即可,而不需要修改 Circle 类。

总结一下,OCP 要求我们尽可能避免修改已有代码,通过扩展来增加新的功能。为了实现 OCP,我们可以尽可能高度封装类的实现细节,基于抽象接口而非具体实现进行设计,遵循依赖倒置原则,避免出现分支语句等。

  1. 里氏替换原则(LSP):

在使用继承关系的类之间,子类必须能够完全替换父类并且保持程序的正确性。

场景示例:

在商城项目中,推出了三种促销方式:

  1. 满减活动,两百以上打八折

  1. 打折活动,全场九折

  1. 返现活动,消费超1000,返200

计一个结算接口及其实现类,该接口的结算方法能够对用户的最终消费金额进行一个计算.计算结果需要满足上面的多种促销方式。

我们可以定义一个接口 Discount 来实现三种不同的促销方式。这个接口中有一个方法 calculateDiscount(),子类必须实现它。

public interface  Discount {
    double calculateDiscount(double price);
}

接下来,我们可以定义三个子类来实现 Discount 接口来实现三种不同的促销方式。

//满减活动,两百以上打八折
class FullDiscount implements Discount {
    private static final double FULL_PRICE = 200.0;
    private static final double DISCOUNT_RATE = 0.8;

    @Override
    public double calculateDiscount(double price) {
        return price >= FULL_PRICE ? price * DISCOUNT_RATE : price;
    }
}
//打折活动,全场九折
class PercentageDiscount implements Discount {
    private static final double DISCOUNT_RATE = 0.9;

    @Override
    public double calculateDiscount(double price) {
        return price * DISCOUNT_RATE;
    }
}
//返现活动,消费超1000,返200
 class CashBackDiscount implements Discount {
    private static final double FULL_PRICE = 1000.0;
    private static final double CASH_BACK = 200.0;

    @Override
    public double calculateDiscount(double price) {
        return price >= FULL_PRICE ? price - CASH_BACK : price;
    }
}

最后,可以通过创建不同的 Discount 子类对象,将其传递给 ShoppingCart 构造函数中,来实现不同的促销方式。

public class ShoppingCart {

    private final Discount discount;

    public ShoppingCart(Discount fullDiscount) {
        this.discount = fullDiscount;
    }

    public double cul(){
        return this.discount.calculateDiscount(3000);
    }

    public static void main(String[] args) {
        Discount fullDiscount = new FullDiscount();
        ShoppingCart shoppingCart1 = new ShoppingCart(fullDiscount);
        double result1 = shoppingCart1.cul();

        Discount percentageDiscount = new PercentageDiscount();
        ShoppingCart shoppingCart2 = new ShoppingCart(percentageDiscount);
        double result2 = shoppingCart2.cul();

        Discount cashBackDiscount = new CashBackDiscount();
        ShoppingCart shoppingCart3 = new ShoppingCart(cashBackDiscount);
        double result3 = shoppingCart3.cul();
    }
}
  1. 接口隔离原则(ISP):

客户端不应该依赖于它不需要的接口。换句话说,一个类不应该被强迫实现它不需要的方法。

// 接口1:定义飞行行为
interface Flyable {
    void fly();
}

// 接口2:定义游泳行为
interface Swimable {
    void swim();
}

// 接口3:定义跳跃行为
interface Jumpable {
    void jump();
}

// 实现类:鸟类
class Bird implements Flyable, Jumpable {
    @Override
    public void fly() {
        System.out.println("I am flying.");
    }
    @Override
    public void jump() {
        System.out.println("I am jumping.");
    }
}

// 实现类:鱼类
class Fish implements Swimable {
    @Override
    public void swim() {
        System.out.println("I am swimming.");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 鸟类只需要依赖飞行和跳跃的接口,而不需要依赖游泳的接口
        Flyable bird = new Bird();
        bird.fly();
        Jumpable bird2 = new Bird();
        bird2.jump();
        
        // 鱼类只需要依赖游泳的接口,而不需要依赖飞行和跳跃的接口
        Swimable fish = new Fish();
        fish.swim();
    }
}

在这个例子中,我们定义了三个接口:Flyable、Swimable、Jumpable,分别定义了飞行、游泳和跳跃的行为。我们实现了两个具体的类:Bird和Fish,分别实现了不同的接口。

在客户端代码中,我们只依赖所需的接口,而不是依赖具体的类。这样,我们就可以遵循接口隔离原则,将依赖关系降到最小,减少了代码的耦合性,提高了系统的可维护性和可扩展性。

  1. 依赖倒置原则(DIP):

高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

// 定义一个接口:开关
interface Switchable {
    void turnOn();
    void turnOff();
}

// 低层模块:灯
class Light implements Switchable {
    @Override
    public void turnOn() {
        System.out.println("Light is on");
    }
    @Override
    public void turnOff() {
        System.out.println("Light is off");
    }
}

// 高层模块:电视
class TV {
    private Switchable switchable;
    public TV(Switchable switchable) {
        this.switchable = switchable;
    }
    public void turnOn() {
        switchable.turnOn();
    }
    public void turnOff() {
        switchable.turnOff();
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建一个灯对象
        Light light = new Light();
        
        // 创建一个电视对象,它依赖于灯对象
        TV tv = new TV(light);
        
        // 打开电视
        tv.turnOn();
        
        // 关闭电视
        tv.turnOff();
    }
}

在这个例子中,我们定义了一个接口Switchable,其中定义了两个方法:turnOn和turnOff。我们创建了一个低层模块Light,它实现了Switchable接口。我们创建了一个高层模块TV,它依赖于Switchable接口而不是具体的实现类。在客户端代码中,我们创建了一个Light对象并将它作为参数传递给TV构造函数。这样,TV就可以控制灯的开关状态。

在这个例子中,我们遵循了接口依赖倒置原则,TV高层模块依赖于Switchable抽象,而不是具体的实现类Light。这样,我们可以轻松地替换掉Light实现类,例如换成一个新的Fan实现类,而不需要修改TV类的代码。这样,我们提高了代码的可维护性和可扩展性。

  1. 迪米特法则(LoD):

一个对象应该对其他对象保持最少的了解。也就是说,一个类不应该知道太多关于其他类的信息,它只需要知道与它交互的类的接口即可。

// 定义一个房间类
class Room {
    private List furnitureList;

    public Room() {
        furnitureList = new ArrayList<>();
        furnitureList.add(new Bed());
        furnitureList.add(new Desk());
    }

    public void showFurniture() {
        for (Furniture furniture : furnitureList) {
            furniture.show();
        }
    }
}

// 定义一个家具抽象类
abstract class Furniture {
    abstract void show();
}

// 定义一个床类
class Bed extends Furniture {
    @Override
    void show() {
        System.out.println("This is a bed.");
    }
}

// 定义一个桌子类
class Desk extends Furniture {
    @Override
    void show() {
        System.out.println("This is a desk.");
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        Room room = new Room();
        room.showFurniture();
    }
}

在这个例子中,我们定义了一个Room类,它包含了一些家具对象,例如床、桌子。我们将家具抽象为Furniture类,并让床、桌子和椅子都继承自Furniture类。在客户端代码中,我们创建了一个Room对象并调用showFurniture方法来展示房间中的所有家具。

这个例子遵循了迪米特法则,Room类只与它直接的朋友通信,即家具对象。Room类不需要知道家具对象的具体实现,只需要知道它们都实现了Furniture抽象类即可。这样,我们可以方便地增加或者减少家具对象,而不需要修改Room类的代码。这样,我们提高了代码的可维护性和可扩展性。

以上就是六大设计原则的讲解,这些设计原则可以帮助我们编写出易于维护、易于扩展、低耦合、高内聚的代码。

你可能感兴趣的:(Java设计模式,设计规范,代码规范,java)