ð§稳固的原则很容易:为您的代码建立强大的基础
#网络开发人员 #教程 #java #designpatterns

软件开发就像建造房屋一样。为了构建强大而可靠的结构,建筑师遵循特定的原则。同样,在软件设计中,我们有可靠的原则可以指导我们构建坚固且可维护的代码。让我们简单地探讨这些原则:

1. S:单一责任原则

想象您有一个工具箱,每个工具都有特定的目的。就这样,班级应该承担明确的责任。它应该做一件事,做得很好。如果一个班级有多个职责,则很难理解,维护和改变而不会影响代码的其他部分。

如何识别:

问自己:“我班级的主要责任是什么?”如果您发现自己在答案中使用“和”一词,则可能会打破单一的责任原则。

例子:

想象一个负责读取文件和处理数据的FileManager类。

class FileManager {
    public String readFile(String filePath) {
        // Logic to read the file and return its content as a string
    }

    public void processData(String data) {
        // Logic to process the data read from the file
    }
}

这违反了单一责任原则。相反,您可以创建两个单独的类:FileReaderDataProcessor,每个类别负责其各自的责任。

class FileReader {
    public String readFile(String filePath) {
        // Logic to read the file and return its content as a string
    }
}

class DataProcessor {
    public void processData(String data) {
        // Logic to process the data read from the file
    }
}
要避免什么:

避免创建尝试做太多的类。另外,无需拥有只有一个功能的多个类。通过保持每个班级专注于单个任务来实现良好的平衡。

2. o:开/关闭原则

将软件视为乐高集合。当您想扩展创建时,您不会修改现有的砖头;您添加新的。同样,在代码中,您应该能够扩展功能而不更改现有代码。

假设您有一个类,该类将二进制数据存储到数据库中:

class BinaryDataDbSaver {
    public void save(Blob binaryData) {
        // ... DB Persistance Logic Here.
    }
}

现在,您想将此二进制数据保存到对象存储中(例如:AWS S3或Microsoft Azure Blob)一种可能的解决方案可以是这样的:

class BinaryDataDbSaver {
    public void saveToDB(Blob binaryData) {
        // ... DB Persistance Logic Here.
    }
    public void saveToBlob(Blob binaryData) {
        // ... DB Persistance Logic Here.
    }
}

在上述解决方案中,要添加新功能,我们修改了现有类,该类已经经过了很好的测试和服务流量。

为了有效地处理此类方案,我们可以从中提取一个接口,并可以提供如下:
的解决方案

public interface BinaryDataSaver {
    void save(Blob binaryData);
}

class BinaryDataDbSaver implements BinaryDataSaver {
    public void save(Blob binaryData) {
        // ... DB Persistance Logic Here.
    }
}

class BinaryDataBlobSaver implements BinaryDataSaver {
    public void save(Blob binaryData) {
        // ... Blob Persistance Logic Here.
    }
}

通过调整该解决方案,我们没有更改任何现有的实现并能够达到我们的目的。

重要的指针:
  • 开放/关闭本金的最初想法与继承有关。
  • 但是,如果子类取决于其父级的实现详细信息,则继承会引入紧密的耦合。
  • 这就是为什么将开放/关闭本金重新定义为多态开放/封闭原则的原因。
  • 它使用界面而不是超级类别来允许您可以轻松替换的不同实现,而无需更改使用它们的代码。
  • 接口已关闭以进行修改,您可以提供新的实现以扩展软件的功能。

3. L:Liskov替代原则

在精心设计的软件系统中,您应该能够用其子类的对象替换父类的对象而不会引起问题。子类应增强父类的行为,而不是限制或改变它。

简单来说:

如果您有水果班和从水果继承的苹果类,则应在您期待一个水果物体的任何地方都可以使用苹果对象。

为什么有用:

通过遵守此原则,您的代码变得更加灵活并允许重复使用。

例子:

考虑鸟类基类和企鹅子类。由于企鹅无法飞行,因此在企鹅对象上称Fly()方法不应导致错误或意外行为。

class Bird {
    public void fly() {
        // Fly behavior for birds
    }
}

class Penguin extends Bird {
    // Penguins cannot fly, so this method should not be here
}

遵守Liskov替代原则,一种可能的解决方案可以是:

abstract class Bird {
    public abstract void fly();
}

class Penguin extends Bird {
    public void fly() {
        // Penguins cannot fly, so this method is not implemented
    }
}

4. I:界面隔离原理

想象一下在餐厅订购一顿饭,并在上面放一块盘子,即使是您不喜欢的菜也是如此。接口隔离原则建议不要强迫客户实施他们不需要的方法。

简单来说:

创建较小的特定界面,而不是使用许多方法的一个大界面。

为什么重要:

这可以防止客户承受不必要的方法的负担,并使代码更加可维护和适应性。

例子:

您有一个大界面,Vehicle,包含startEngine()accelerate()playRadio()之类的方法。如果实现Vehicle的类对playRadio()没有用,它被迫实施它,违反了接口隔离原理。

interface Vehicle {
    void startEngine();
    void accelerate();
    void playRadio();
}

class Car implements Vehicle {
    public void startEngine() {
        // Car specific engine starting logic
    }

    public void accelerate() {
        // Car specific acceleration logic
    }

    public void playRadio() {
        // Radio playing logic - not needed for all vehicles
    }
}

要解决此问题,您可以将接口分为较小的特定接口:

interface Vehicle {
    void startEngine();
    void accelerate();
}

interface RadioPlayable {
    void playRadio();
}

class Car implements Vehicle, RadioPlayable {
    public void startEngine() {
        // Car specific engine starting logic
    }

    public void accelerate() {
        // Car specific acceleration logic
    }

    public void playRadio() {
        // Radio playing logic - implemented only for vehicles that can play radio
    }
}
重要的提示:

违反界面隔离原则可能会导致Liskov替代原则的问题,如果客户被迫实施他们不正确支持的方法。

5. D:依赖性反转原理

将软件模块视为拼图拼图中的零件。您没有直接连接每件件,而是使用允许不同零件合并在一起的连接器。同样,依赖性反转原理会根据抽象(接口)而不是具体的实现鼓励。

简单来说:

您的课程应依赖于接口,而不是特定类。

为什么重要:

通过取决于抽象,您的代码变得更加灵活,可维护且易于测试。您可以轻松地交换实现而无需更改依赖类。

让我们考虑以下代码,该代码代表了非常古老的一代的MacBook,并想象它使用有线键盘和鼠标。

class MacBook {
    private WiredKeyboard keyboard;
    private WiredMouse mouse;

    public MacBook(WiredKeyboard keyboard, WiredMouse mouse) {
        this.keyboard = keyboard;
        this.mouse = mouse;
    }
}

在上面的示例中,如果您必须将WiredKeyboard替换为WirelessKeyboard,那么我们需要更改现有类,这可能会违反 open/lother lincipal 并导致了错误的代码。

相反,MacBook类应依靠接口,而不是实现者类。上述问题可以如下所示:

interface Keyboard {
    //... Methods
}

interface Mouse {
    //.. Methods
}

class WiredKeyboard implements Keyboard {
    //... Implementations
}
class WirelessKeyboard implements Keyboard {
    //... Implementations
}

class WiredMouse implements Mouse {
    //... Implementations
}
class WirelessMouse implements Mouse {
    //... Implementations
}

class MacBook {
    private Keyboard keyboard;
    private Mouse mouse;

    public MacBook(Keyboard keyboard, Mouse mouse) {
        this.keyboard = keyboard;
        this.mouse = mouse;
    }
}

现在,如果我们需要更改组件,我们需要注入其他类型的子类,并且我们的工作将很容易完成,而无需触摸MacBook类实现。

结论

通过遵循这些坚实的原则,您可以创建易于理解,维护和扩展的软件,就像为项目建立强大的基础。