如何练习OOP设计的SOLID原理?

Rub*_*uby 16 design-patterns dependency-injection solid-principles

我是SOLID原理的新手,但我理解它.我的主要问题是很难设计我的类以遵循SOLID特别是依赖倒置.有时将整个逻辑写入过程模式而不是使用SOLID很容易.

例如:

假设我们正在创建一个考勤监控系统,我们有逻辑(或程序)扫描员工指纹,获取它的ID,确定它是否有效,确定他在什么时间,写入登录信息到数据库,并显示它是否成功.

用一堆'if else',循环和开关以程序方式编写它很容易.但在未来,我将遭遇"代码债务".

如果我们在这里应用SOLID原则.我知道我们需要有一些像'AttendanceServiceClass'这样的对象,它有一个像'scanEmployeeID()','processthislogin()'或'isItsucessful()'这样的方法.我知道这个类依赖于存储库,userinfo和其他对象.

基本上我的问题是分析类的设计及其依赖性

分析课程设计的一步一步是什么?

对不起我的英语不好.

Hen*_*los 38

首先,固体不是一个原则,它代表5种不同的原则:

  • SRP(单一责任原则):您的班级应该只有一个明确的责任;
  • OCP(开放封闭原则):您的课程应该开放以进行扩展,但是关闭以进行修改;
  • LSP(Liskov的替换原则):这个指导您决定是否使用类A和之间的继承关系B.只要派生类的所有对象B都可以被其父类的对象替换A而没有任何功能损失,继承就是合适的;
  • ISP(接口隔离原则):声明不应强迫客户端依赖它不使用的方法;
  • DIP(依赖注入/反转):声明高级模块不应该依赖于低级模块.

这些原则是指南,但并不意味着您每次都必须严格使用它们.

从你的描述中,我可以看出你的主要困难是想到OO.你还在考虑如何做事,这是一种程序性思维.但在OOP中,更重要的是决定谁将做这些事情.

考虑DI,使用您的示例,让我们看看您的场景:

public class AttendanceService {
    // other stuff...

    public boolean scanEmployeeId() {
        // The scanning is made by an barcode reader on employee's name tag
    }
}
Run Code Online (Sandbox Code Playgroud)

这里有什么问题?

好吧,首先,这段代码违反了SRP:如果身份验证过程发生了变化怎么办?如果公司决定名称标签不安全并安装生物识别系统?好吧,这里有一个改变你的类的原因,但是这个类不仅仅进行身份验证,而是执行其他操作,因此,还有其他原因需要更改.SRP声明您的课程应该有一个改变的理由.

它也违反了OCP:如果有另一种认证方法可用,我希望能够按照我的意愿使用该怎么办?我不能.要更改auth方法,我必须修改类.

它违反了ISP:为什么一个ServiceAttendance对象有一个员工认证的方法,如果它只是提供服务出勤?


让我们稍微改进一下:

public class BarCodeAuth {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class AttendanceService {
    private BarCodeAuth auth;
    public AttendanceClass() {
        this.auth = new BarCodeAuth();
    }

    public void doOperation() {
        if(this.auth.authenticate()) {
           // do stuff..
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在好一点了.我们解决了SRPISP的问题,但是如果你想得更好,它仍然违反了OCP,现在违反了DIP.问题是AttendanceService与之紧密结合BarCodeAuth.我仍然无法在不触碰的情况下更改auth方法AttendanceService.

现在让我们一起应用OCPDIP:

public interface AuthMethod {
    public boolean authenticate();
}

public class BarCodeAuth implements AuthMethod {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class BiometricAuth implements AuthMethod {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class FooBarAuth implements AuthMethod {
    public boolean authenticate() {
        // Authenticates...
    }
}

public class AttendanceClass {
    private AuthMethod auth;
    public AttendanceClass(AuthMethod auth) {
        this.auth = auth;
    }

    public void doOperation() {
        if(this.auth.authenticate()) {
           // do stuff..
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我能做到:

new AttendanceClass(new BarCordeAuth());
new AttendanceClass(new BiometricAuth());
Run Code Online (Sandbox Code Playgroud)

要改变行为,我不需要触摸课程.如果出现其他一些auth方法,我只需要实现它,尊重接口并准备使用(还记得OCP吗?).这是因为我正在使用DIPServiceAttendance.虽然它需要一种身份验证方法,但创建一种方法并不是它的责任.实际上,对于这个对象,认证方法并不重要,它只需要知道调用者(用户)是否被授权做他正在尝试做的事情.

这就是DIP的全部内容:您的组件应该依赖于抽象,而不是实现.

  • 我喜欢你如何开始使用`这些原则是指南,但并不意味着你每次都必须严格使用它们.我完全同意,我们不能总是一直实施它.有时我们必须妥协并做必须完成的工作才能达到要求.我也喜欢保持简单(KIS)并且不想过于完美并最终得到一个打印"Hello"的类和另一个打印"World"的类,这只是夸张但却发生了. (2认同)

Dmi*_*try 8

不是专门针对SOLID,但值得一提的是Jeff Bay 非常有趣的OOP 培训方法:面向对象的健美操.我们的想法是,您可以尝试在非现实的小项目中遵循一套非常严格的规则.

The Rules

1. One level of indentation per method
2. Don’t use the ELSE keyword 
3. Wrap all primitives and Strings
4. First class collections
5. One dot per line
6. Don’t abbreviate
7. Keep all entities small
8. No classes with more than two instance variables
9. No getters/setters/properties
Run Code Online (Sandbox Code Playgroud)

通过暂停怀疑,并在一个小型的1000线项目中严格应用这些规则,您将开始看到一种与设计软件截然不同的方法.一旦你编写了1000行代码,练习就完成了,你可以放松一下,然后再回到使用这9条规则作为指导.

这是一项艰苦的工作,特别是因为其中许多规则并非普遍适用.事实是,有时课程略多于50行.但是,考虑将这些责任转移到他们自己的真正的一流对象中会发生什么,这是非常有价值的.正在开发这种类型的思维,这是练习的真正价值.因此,尽可能扩展您想象的极限,看看您是否开始以新的方式考虑您的代码.


Fen*_*ndy 7

有时将整个逻辑写入过程模式而不是使用SOLID很容易

我不能同意,程序员在程序模式中处理代码更容易.这使得习惯于程序编程的程序员难以使用OOP.

但是我发现首先编写通用接口和消费者更容易,而不是破坏设计用于较小模块的接口.这是一种Test First Development -> Red, green, refactor练习.(请注意,如果你想实现neat design,请考虑使用TDD而不是本指南.本指南只是TDD的一小部分)

说我们要创建ServiceAttendance要做的事情scanEmployeeID.我们将有类似的界面(请注意示例是在C#命名):

public interface IServiceAttendance{
    bool ScanEmployeeId();
}
Run Code Online (Sandbox Code Playgroud)

请注意,我决定返回bool而不是void的方法来确定成功/失败操作.请注意下面的消费者示例没有实现任何DI,因为我只想展示如何使用它.然后在消费者中,我们可以:

public void ConsumeServiceAttendance(){
    IServiceAttendance attendance = Resolve<IServiceAttendance>();
    if(attendance.ScanEmployeeId()){
        // do something
    }
}
Run Code Online (Sandbox Code Playgroud)

消费者得出结论.现在我们转向实施.假设您可以使用过程编程开发它并获得单片代码块.您可以使用pseu-like语句声明实现.

public class ServiceAttendance : IServiceAttendance{
    public bool ScanEmployeeId(){
        bool isEmpValid = false;
        // 1 scan the employee id
        // 2 validate the login
        // 3 if valid, create the login session
        // 4 notify the user
        return isEmpValid;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们在这一个操作中有4个步骤要完成.我的主要是,不要在一种方法中做3个外观过程,所以我可以简单地将3和4重构为一个过程.现在我们有

public class ServiceAttendance : IServiceAttendance{
    public bool ScanEmployeeId(){
        bool isEmpValid = false;
        // 1 scan the employee id
        // 2 validate the login
        // 3 if valid, create the login session and notify the user
        return isEmpValid;
    }
}
Run Code Online (Sandbox Code Playgroud)

这个,我们有3个主要操作.我们可以通过分解操作来分析是否需要创建更小的模块.假设我们要打破第二次操作.我们可以得到:

// 2 validate the login
// 2.1 check if employee id matches the format policy
// 2.2 check if employee id exists in repository
// 2.3 check if employee id valid to access the module
Run Code Online (Sandbox Code Playgroud)

故障操作本身足以将第二个模块分解为另一个较小的模块.对于2.22.3,我们需要注入一个较小的模块.仅仅因为它需要依赖于存储库,因此需要注入.同样的情况适用于操作步骤1 scan the employee id,因为它需要依赖于指纹扫描器,因此扫描器处理程序必须在分离的模块中实现.

我们总是可以分解操作,因为我们可以在以下方面进行2.1:

// 2.1 check if employee id matches the format policy
// 2.1.1 employee id must match the length
// 2.1.2 employee id must has format emp#####
Run Code Online (Sandbox Code Playgroud)

现在我不能确定,如果2.1.12.1.2需要被分解成2个独立的模块,它是由你来决定.现在我们得到了接口,然后我们就可以开始实现了.期望exceptions在验证期间抛出,或者您需要传递自定义类来处理错误消息.

  • 绝对.这就是为什么我在示例中提到"请注意下面的消费者示例没有实现任何DI,因为我只想展示如何使用它" (2认同)