カプセル化の過剰使用に悩まされますか?


11

さまざまなプロジェクトのコードに、コードの臭いや悪いことのように見える何かに気づきましたが、対処できません。

「きれいなコード」を書き込もうとしている間、コードを読みやすくするためにプライベートメソッドを使いすぎる傾向があります。問題は、コードが確かにきれいであるが、テストするのが難しいことです(そう、私はプライベートメソッドをテストできることを知っています...)、一般的に私には悪い習慣のようです。

次に、.csvファイルからデータを読み取り、顧客のグループ(さまざまなフィールドと属性を持つ別のオブジェクト)を返すクラスの例を示します。

public class GroupOfCustomersImporter {
    //... Call fields ....
    public GroupOfCustomersImporter(String filePath) {
        this.filePath = filePath;
        customers = new HashSet<Customer>();
        createCSVReader();
        read();
        constructTTRP_Instance();
    }

    private void createCSVReader() {
        //....
    }

    private void read() {
        //.... Reades the file and initializes the class attributes
    }

    private void readFirstLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readSecondLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readCustomerLine(String[] inputLine) { 
        //.... Method used by the read() method
    }

    private void constructGroupOfCustomers() {
        //this.groupOfCustomers = new GroupOfCustomers(**attributes of the class**);
    }

    public GroupOfCustomers getConstructedGroupOfCustomers() {
        return this.GroupOfCustomers;
    }
}

ご覧のとおり、クラスにはプライベートメソッドを呼び出してジョブを完了するコンストラクターしかありませんが、一般的には良い方法はありませんが、メソッドをパブリックにするのではなく、クラスのすべての機能をカプセル化することを好みますクライアントは次のように動作するはずです。

GroupOfCustomersImporter importer = new GroupOfCustomersImporter(filepath)
importer.createCSVReader();
read();
GroupOfCustomer group = constructGoupOfCustomerInstance();

これは、クライアントクラスに実装の詳細を煩わせる無駄なコード行をクライアント側のコードに入れたくないため、これが好きです。

それで、これは実際に悪い習慣ですか?はいの場合、どうすればそれを回避できますか?上記は単なる例にすぎないことに注意してください。同じ状況がもう少し複雑なもので起こっていると想像してください。

回答:


17

クライアントから実装の詳細を隠したい限り、実際には正しい軌道に乗っていると思います。クライアントに表示されるインターフェイスが、考えられる最も単純で簡潔なAPIになるようにクラスを設計します。クライアントは実装の詳細に「悩まされない」だけでなく、そのコードの呼び出し元を変更する必要を心配せずに、基になるコードをリファクタリングすることもできます。異なるモジュール間のカップリングを減らすことには、いくつかの本当の利点があります。そのために努力する必要があります。

したがって、私が提供したアドバイスに従えば、コードで既に気付いたもの、つまりパブリックインターフェイスの背後に隠れたままで簡単にアクセスできないロジックの束になってしまいます。

理想的には、パブリックインターフェイスに対してのみクラスを単体テストできる必要があり、外部依存関係がある場合は、テスト対象のコードを分離するためにfake / mock / stubオブジェクトを導入する必要があります。

ただし、これを実行しても、クラスのすべての部分を簡単にテストできないと感じる場合は、1つのクラスが多すぎます。精神でSRP原理、あなたは何に従うことができマイケル羽は「スプラウトクラスパターン」を呼び出し、新しいクラスにあなたの元のクラスのチャンクを抽出します。

この例では、テキストファイルの読み取りも担当するインポータークラスがあります。オプションの1つは、ファイル読み取りコードのチャンクを個別のInputFileParserクラスに抽出することです。現在、これらのプライベート関数はすべて公開されているため、簡単にテストできます。同時に、パーサークラスは外部クライアントに見えるものではありません(「内部」とマークし、ヘッダーファイルを公開しないか、APIの一部として単にアドバタイズしません)。そのインターフェースが短くて甘いままである輸入業者。


1

多くのメソッドコールをクラスコンストラクターに挿入する代わりに(一般的に避ける習慣です)、代わりにファクトリメソッドを作成して、クライアントに迷惑をかけたくない余分な初期化をすべて処理することができますそばに。これは、constructGroupOfCustomers()メソッドをパブリックのように見せるためにメソッドを公開し、それをクラスの静的メソッドとして使用することを意味します:

GroupOfCustomersImporter importer = 
    new GroupOfCustomersImporter.CreateInstance();

または、おそらく別のファクトリクラスのメソッドとして:

ImporterFactory factory = new ImporterFactory();
GroupOfCustomersImporter importer = factory.CreateGroupOfCustomersImporter();

これらは、私の頭の上からまっすぐに考えられる最初のオプションです。

また、本能があなたに何かを伝えようとしていることを考慮する価値があります。あなたの腸がコードが匂い始めていると言ったら、おそらく匂いがします、そしてそれが臭いする前にそれについて何かをする方が良いです!表面上はコード自体に本質的な問題はないかもしれませんが、簡単な再チェックとリファクタリングを行うと、問題についての考えを解決するのに役立ちます。カードの潜在的な家。この特定のケースでは、コンストラクターの一部の責任をファクトリに委任することで、またはファクトリーから呼び出されることで、クラスとその潜在的な将来の子孫のインスタンス化をより高度に制御できるようになります。


1

コメントには長すぎたため、自分の答えを追加しました。

カプセル化とテストはかなり相反することは間違いありません-ほとんどすべてを隠すと、テストするのが難しくなります。

ただし、ファイル名ではなくストリームを指定することで、サンプルをよりテストしやすくすることができます。この方法では、既知のcsvをメモリから、または必要に応じてファイルから提供するだけです。また、httpサポートを追加する必要がある場合、クラスがより堅牢になります;)

また、lazy-initの使用方法を調べてください。

public class Csv
{
  private CustomerList list;

  public CustomerList get()
  {
    if(list == null)
        load();
     return list;
  }
}

1

コンストラクターは、一部の変数またはオブジェクトの状態を初期化する以外は、あまり多くのことをすべきではありません。コンストラクターでやりすぎると、ランタイム例外が発生する可能性があります。私はクライアントがこのような何かをするのを避けようとします:

MyClass a;
try {
   a = new MyClass();
} catch (MyException e) {
   //do something
}

代わりに:

MyClass a = new MyClass(); // Or a factory method
try {
   a.doSomething();
} catch (MyException e) {
   //do something
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.