20

私はSOLIDの原則に不慣れですが、理解しています。私の主な問題は、SOLID、特に依存関係の反転に従うようにクラスを設計するのに苦労していることです。場合によっては、SOLID を使用するよりも、ロジック全体を手続き型パターンに記述する方が簡単です。

例えば:

出席監視システムを作成しているとしましょう。従業員の指紋をスキャンし、ID を取得し、有効かどうかを判断し、従業員の勤務時間を判断し、ログイン情報をデータベース、そしてそれが成功したかどうかを示します。

「if else」、ループ、スイッチの束を使って手続き的にこれを書くのは簡単です。しかし、将来的には「コードの負債」に苦しむことになります。

ここで SOLID 原則を適用するとします。「scanEmployeeID()」、「processthislogin()」、「isItsucessful()」などのメソッドを持つ「AttendanceServiceClass」のようなオブジェクトが必要であることはわかっています。また、このクラスがリポジトリ、userinfo、およびその他のオブジェクトに依存していることもわかっています。

基本的に私の問題は、クラスの設計とその依存関係について分析することです

クラスの設計を段階的に分析する方法は何ですか?

私の英語でごめんなさい。

4

5 に答える 5

47

まず第一に、ソリッドは 1 つの原則ではなく、5 つの異なる原則を表しています。

  • SRP (Single Responsibility Principle) : コンポーネントには、変更する理由が 1 つある必要があります。
  • OCP (Open-Closed Principle) : コンポーネントは拡張のために開いている必要がありますが、変更のために閉じている必要があります。
  • LSP (Liskov's Substitution Principle) : これは、クラス間の階層関係を継承によって構築する必要があるかどうかを判断するのに役立ちAますB。継承は、派生クラスのすべてのオブジェクトを、機能を失うことなくB親クラスのオブジェクトに置き換えることができる場合に適しています。A
  • ISP (Interface Segregation Principle) : どのコンポーネントも、使用していないメソッドに依存することを強制されるべきではないと述べています。
  • DIP (Dependency Injection/Inversion) : 高レベル コンポーネントが低レベル コンポーネントに依存してはならないことを示します。

これらの原則はガイドですが、毎回厳密に使用する必要があるという意味ではありません。

あなたの説明から、あなたの主な困難は OO を考えることにあることがわかります。あなたはまだ物事を行う方法について考えていますが、これは手続き型の考え方です。しかし、OOP では、誰がこれらのことを行うかを決定することがより重要です

あなたの例を使用して、DIについて考えて、あなたのシナリオを見てみましょう:

public class AttendanceService {
    // other stuff...
    
    public boolean scanEmployeeId() {
        // The scanning is made by an barcode reader on employee's name tag
    }
}

ここで何が問題なのですか?

まず、このコードはSRPに違反しています: 認証プロセスが変更されたらどうなるでしょうか? 会社が名前タグが安全でないと判断し、生体認証システムをインストールしたら? クラスを変更する理由はここにありますが、このクラスは認証だけを行うのではなく、他のことを行うため、変更する理由は他にもあるでしょう。SRP では、クラスを変更する理由は 1 つだけにする必要があると述べています。

また、 OCPにも違反しています。利用可能な別の認証方法があり、希望どおりに使用できるようにしたい場合はどうすればよいですか? 私はできません。認証方法を変更するには、クラスを変更する必要があります。

それは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..
        }
    }
}

これで少しは良くなりました。SRPISPの問題は解決しましたが、よく考えれば、依然としてOCPに違反しており、現在はDIPに違反しています。問題は、AttendanceServiceと密結合していることBarCodeAuthです。に触れずに認証方法を変更することはまだできません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..
        }
    }
}

今私はできる:

new AttendanceClass(new BarCordeAuth());
new AttendanceClass(new BiometricAuth());

動作を変更するために、クラスに触れる必要はありません。他の認証方法が表示された場合は、インターフェイスを尊重して実装するだけで、すぐに使用できます ( OCPを覚えていますか?)。これは、私がDIP onを使用しているためですServiceAttendance。認証方法が必要ですが、それを作成する責任はありません。実際、このオブジェクトの場合、認証の方法は問題ではなく、発信者 (ユーザー) がしようとしていることを実行する権限があるかどうかを知る必要があるだけです。

これはすべてDIPに関するものです。コンポーネントは、実装ではなく抽象化に依存する必要があります。

于 2013-05-20T01:07:37.610 に答える
8

SOLID を使用するよりも、ロジック全体を手続き型パターンに記述する方が簡単な場合があります。

これ以上同意することはできません。私たちプログラマーは、手続き型パターンでコードを処理する方が簡単です。これにより、手続き型プログラミングに慣れているプログラマーにとって OOP は難しくなります。

ただし、小さなモジュール用に設計されたインターフェイスを壊すよりも、一般的なインターフェイスとコンシューマーを最初に作成する方が簡単であることがわかりました。これは一種のTest First Development -> Red, green, refactor練習です。( を達成したい場合はneat design、このガイドの代わりに TDD に従うことを検討してください。このガイドは、TDD の実行のほんの一部です)

ServiceAttendanceto doを作成したいとしましょうscanEmployeeID。次のようなインターフェイスがあります (例は C# の命名にあることに注意してください)。

public interface IServiceAttendance{
    bool ScanEmployeeId();
}

なお、動作の成否はvoidではなくboolを返す方法に決めています。以下の消費者の例では、DI を使用する方法を示したいだけなので、DI を実装していないことに注意してください。次に、コンシューマーでは、次のことができます。

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

これでコンシューマは終了です。では実装に移ります。手続き型プログラミングを使用して開発でき、モノリシック コード ブロックを取得したとします。疑似ステートメントで実装を述べることができます。

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;
    }
}

これで、この 1 回の操作で 4 つの手順を実行できます。私の原則は、1 つのメソッドで 3 つ以上のファサード プロセスを実行しないことです。そのため、3 と 4 を 1 つのプロセスに単純にリファクタリングできます。今、私たちは持っています

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;
    }
}

これには、3つの主要な操作があります。操作を分解することで、より小さなモジュールを作成する必要があるかどうかを分析できます。2 番目の操作を中断したいとします。私たちは手に入れる:

// 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

分割操作自体は、2 番目のモジュールを別の小さなモジュールに分割するのに十分明らかです。2.2との場合2.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#####

現在2.1.12.1.22 つの個別のモジュールに分割する必要があるかどうかはわかりません。決定するのはあなた次第です。これでインターフェイスを取得できたので、実装を開始できます。検証中にスローすることを期待してexceptionsください。そうしないと、カスタム クラスを渡してエラー メッセージを処理する必要があります。

于 2013-05-20T02:39:13.593 に答える
3

まず、出席システムのさまざまな部分について考えてみましょう。ユーザー インターフェイス、指紋スキャナー、データベース リポジトリ、ログイン プロセス、およびワークフロー。このシステムを設計するには、部品を分離して設計し、それらをシステムとして接続します。

大まかな設計は、システムの次の部分に関するものです。

  • 指紋スキャナーとリスナー
  • 出席サービス
  • 従業員リポジトリ
  • ログイン リポジトリ
  • ユーザーインターフェース
  • 出勤ワークフローコントローラー
  • 指紋署名

次のコード リストでは、設計原則の特定の側面がすでに表示されています。

  • SRP - 1 つのエンティティが 1 つのジョブを担当
  • LoD - デメテルの法則 - 直接の友達とのみ話してください。Controller はリポジトリについて何も知らないことに気付くでしょう。
  • DbC (契約による設計) - インターフェイスに対する作業
  • 依存性注入と IoC の使用 - コンストラクター注入とメソッド注入
  • ISP (Interface Segregation Principle) - 無駄のないインターフェース
  • OCP - 派生クラスのインターフェイス メソッドをオーバーライドするか、別の実装を渡します。注入されたインターフェイスは、クラスを変更する必要なく動作を拡張できます。

この多くの考えに基づいて、システムは次のように機能する可能性があります。

[さらに改善し、不足しているロジックを追加できます。簡単な実装を含む非常に簡単な設計概要を提供します。]

コード リスト

interface IAttedanceController
{
    run();
}

interface IFingerprintHandler
{
    void processFingerprint(IFingerprintSignature fingerprintSignature);
}

interface IFingerprintScanner
{
    void run(IFingerprintHandler fingerprintHandler);
}

interface IAttendanceService
{
    void startService();
    void stopService();
    bool attempEmployeeLogin(IFingerprintSignature fingerprintSignature);
    string getFailureMessage();
}

interface ILoginRepository
{
    bool loginEmployee(IEmployee employee, DateTime timestamp);
    void open();
    void close();
}

interface IEmployeeRepository
{
    IEmployee findEmployee(IFingerprintSignature fingerprintSignature);
    void open();
    void close();
}

//-----------------------------------------

class AttendanceService : IAttendanceService
{
    private IEmployeeRepository _employeeRepository;
    private ILoginRepository _loginRepository;
    private string _failureMessage;

    public class AttendanceService(
        IEmployeeRepository employeeRepository,
        ILoginRepository loginRepository)
    {
        this._employeeRepository = employeeRepository;
        this._loginRepository = loginRepository;
    }

    public bool attempEmployeeLogin(IFingerprintSignature fingerprintSignature)
    {
        IEmployee employee = this._employeeRepository.findEmployee(fingerprintSignature);

        if(employee != null)
        {
            //check for already logged in to avoid duplicate logins..

            this._loginRepository.loginEmployee(employee, DateTime.Now);
            //or create a login record with timestamp and insert into login repository

            return true;
        }
        else
        {
            this._failureMessage = "employee not found";
            return false;
        }
    }

    public string getFailureMessage()
    {
        return "reason for failure";
    }

    public void startService()
    {
        this._employeeRepository.open();
        this._loginRepository.open();
    }

    public void stopService()
    {
        this._employeeRepository.close();
        this._loginRepository.close();
    }
}

//-----------------------------------------

class AttendanceController : IAttedanceController, IFingerprintHandler
{
    private ILoginView _loginView;
    private IAttendanceService _attedanceService;
    private IFingerprintScanner _fingerprintScanner;

    public AttendanceController(
        ILoginView loginView,
        IAttendanceService attendanceService,
        IFingerprintScanner fingerprintScanner)
    {
        this._loginView = loginView;
        this._attedanceService = attedanceService;
        this._fingerprintScanner = fingerprintScanner;
    }

    public void run()
    {
        this._attedanceService.startService();
        this._fingerprintScanner.run(this);
        this._loginView.show();
    }

    public void IFingerprintHandler.processFingerprint(IFingerprintSignature fingerprintSignature)
    {
        if(this._attedanceService.login(fingerprintSignature))
        {
        this._loginView.showMessage("Login successful");
        }
        else
        {
        string errorMessage = string getFailureMessage();
        this._loginView.showMessage("errorMessage");
        }

        // on return the fingerprint monitor is ready to take another finter print
    }
}

//-----------------------------------------

App.init()
{
    // Run app bootstrap
    // Initialize abstract factories or DI containers

    IAttedanceController attedanceController = DIContainer.resolve("AttedanceController");
    attedanceController.run();
}

//-----------------------------------------
于 2013-05-20T03:53:02.130 に答える