JavaRush /Java Blog /Random-JA /コードをクリーンにする確かな原則
Paul Soia
レベル 26
Kiyv

コードをクリーンにする確かな原則

Random-JA グループに公開済み
ソリッドとは何ですか? SOLID の頭字語は次のとおりです。 - S:単一 責任の原則。- O:開閉原理 。- L:リスコフ置換原理 。- I:インターフェース分離原則 。- D:依存性逆転の原則 。SOLID 原則は、モジュールの設計方法をアドバイスします。アプリケーションが構築されるレンガ。原則の目的は、次のようなモジュールを設計することです: - 変更を促進する - 簡単に理解できる - 再利用可能 それでは、例を使用してそれらが何であるかを見てみましょう。
class MainRepository(
    private val auth: FirebaseAuth
) {
    suspend fun loginUser(email: String, password: String) {
        try {
            auth.signInWithEmailAndPassword(email, password)
        } catch (e: Exception) {
            val file = File("errors.txt")
            file.appendText(text = e.message.toString())
        }
    }
}
この例から何がわかるでしょうか? FirebaseAuth はコンストラクターで渡され、それを使用してログインを試行します。応答でエラーを受け取った場合は、メッセージをファイルに書き込みます。では、このコードの何が問題なのかを見てみましょう。1. S:単一責任の原則 : クラス (または関数/メソッド) は 1 つのことだけを担当する必要があります。クラスが複数の問題を解決する責任がある場合、これらの問題の解決策を実装するそのサブシステムは相互に接続されます。このようなサブシステムの 1 つが変更されると、別のサブシステムも変更されます。この例では、loginUser 関数は、ログインとファイルへのエラーの書き込みという 2 つのことを担当します。責任を 1 つにするには、エラーを記録するロジックを別のクラスに配置する必要があります。
class FileLogger{
    fun logError(error: String) {
        val file = File("errors.txt")
        file.appendText(text = error)
    }
}
class MainRepository(
    private val auth: FirebaseAuth,
    private val fileLogger: FileLogger,
) {
    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        } catch (e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }
}
エラー記録ロジックは別のクラスに移動され、loginUser メソッドの役割は承認という 1 つだけになりました。エラー ログ ロジックを変更する必要がある場合は、loginUser 2 関数ではなく、FileLogger クラスでこれを行います。O: オープンクローズ原則: ソフトウェア エンティティ (クラス、モジュール、関数) は拡張機能に対してオープンである必要があります。ただし改造用ではありません。この文脈において、拡張に対する開放性とは、必要に応じてクラス、モジュール、または関数に新しい動作を追加できる機能であり、変更に対する閉鎖性とは、ソフトウェア エンティティのソース コードの変更を禁止することです。一見すると、これは複雑で矛盾しているように思えます。しかし、よく見てみると、その原理は非常に論理的です。OCP の原則に従って、ソフトウェアは既存のコードを変更するのではなく、新しいコードを追加することによって変更されます。つまり、最初に作成されたコードは「そのまま」で安定しており、実装の継承または抽象インターフェイスとポリモーフィズムの使用を通じて、新しい機能が導入されます。それでは、FileLogger クラスを見てみましょう。ログを別のファイルに書き込む必要がある場合は、名前を変更できます。
class FileLogger{
    fun logError(error: String) {
        val file = File("errors2.txt")
        file.appendText(text = error)
    }
}
ただし、すべてのログは新しいファイルに書き込まれますが、おそらくこれは必要ありません。しかし、クラス自体を変更せずにロガーを変更するにはどうすればよいでしょうか?
open class FileLogger{

    open fun logError(error: String) {
        val file = File("error.txt")
        file.appendText(text = error)
    }

}

class CustomErrorFileLogger : FileLogger() {

    override fun logError(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
FileLogger クラスと logError 関数をオープンとしてマークし、新しい CustomErrorFileLogger クラスを作成して、新しいロギング実装を作成します。その結果、このクラスは機能を拡張することはできますが、変更は受け付けられていません。3. L:リスコフ置換原理 。サブクラスがスーパークラスの代替として機能できることが必要です。この原則の目的は、プログラムを中断することなく、派生元の親クラスの代わりに子孫クラスを使用できるようにすることです。コードがクラスの型をチェックしていることが判明した場合、置換原則に違反します。後継クラスを次のように書いた場合:
class CustomErrorFileLogger : FileLogger() {

    fun customErrorLog(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
次に、リポジトリ内の FileLogger クラスを CustomErrorFileLogger に置き換えましょう。
class MainRepository(
    private val auth: FirebaseAuth,
    private val fileLogger: CustomErrorFileLogger,
) {

    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        }catch(e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }

}
この場合、logError は親クラスから呼び出されるか、fileLogger.logError(e.message.toString()) の呼び出しを fileLogger.customErrorLog(e.message.toString()) に変更する必要があります。 : インターフェース分離原理 特定のクライアント向けに設計された高度に専門化されたインターフェイスを作成します。クライアントは、使用しないインターフェイスに依存すべきではありません。複雑そうに聞こえますが、実際はとてもシンプルです。たとえば、FileLogger クラスをインターフェイスにして、それにもう 1 つの関数を追加してみましょう。
interface FileLogger{

    fun printLogs()

    fun logError(error: String) {
        val file = File("errors.txt")
        file.appendText(text = error)
    }

}
これで、すべての子孫クラスで printLogs 関数が必要ない場合でも、すべての子孫で printLogs 関数を実装する必要があります。
class CustomErrorFileLogger : FileLogger{

    override fun printLog() {

    }

    override fun logError(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
すると空の関数ができてしまいますが、これはコードのクリーンさにとって良くありません。代わりに、インターフェイスでデフォルト値を作成し、それが必要なクラスでのみ関数をオーバーライドできます。
interface FileLogger{

    fun printLogs() {
        //Howая-то дефолтная реализация
    }

    fun logError(error: String) {
        val file = File("errors.txt")
        file.appendText(text = error)
    }

}
class CustomErrorFileLogger : FileLogger{

    override fun logError(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
FileLogger インターフェイスを実装するクラスがよりクリーンになります。5. D:依存性逆転の原則 。依存関係のオブジェクトは具体的なものではなく、抽象的なものである必要があります。メインクラスに戻りましょう。
class MainRepository(
    private val auth: FirebaseAuth,
    private val fileLogger: FileLogger,
) {

    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        } catch (e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }

}
ここですでにすべての設定と修正が完了しているようです。しかし、まだ変更する必要がある点がもう 1 つあります。これは FirebaseAuth クラスを使用しています。ある時点で認証を変更し、Firebase 経由ではなく、たとえばある種の API リクエストを使用してログインする必要がある場合はどうなりますか? そうなると、多くのことを変更しなければならなくなりますが、それは望ましくありません。これを行うには、関数signInWithEmailAndPassword(電子メール: String, パスワード: String) を使用してインターフェイスを作成します。
interface Authenticator{
    fun signInWithEmailAndPassword(email: String, password: String)
}
このインターフェースは私たちの抽象化です。次に、ログインの具体的な実装を行います。
class FirebaseAuthenticator : Authenticator{

    override fun signInWithEmailAndPassword(email: String, password: String) {
        FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password)
    }

}
class CustomApiAuthenticator : Authenticator{

    override fun signInWithEmailAndPassword(email: String, password: String) {
        //другой способ логина
    }

}
そして、「MainRepository」クラスでは、特定の実装には依存せず、抽象化のみに依存するようになりました。
class MainRepository (
    private val auth: Authenticator,
    private val fileLogger: FileLogger,
) {

    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        }catch(e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }

}
ここで、認証方法を変更するには、モジュール クラスの 1 行だけを変更する必要があります。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION