软件开发就像建造房屋一样。为了构建强大而可靠的结构,建筑师遵循特定的原则。同样,在软件设计中,我们有可靠的原则可以指导我们构建坚固且可维护的代码。让我们简单地探讨这些原则:
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
}
}
这违反了单一责任原则。相反,您可以创建两个单独的类:FileReader
和DataProcessor
,每个类别负责其各自的责任。
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 并导致了错误的代码。 P>
相反,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
类实现。
结论
通过遵循这些坚实的原则,您可以创建易于理解,维护和扩展的软件,就像为项目建立强大的基础。