[Clean Code] 6. オブジェクトとデータ構造


Clean codeの本の内容をまとめた文章です.
著作権に問題がある場合は、[email protected]メールを送ってくれれば、すぐに削除します.
第6章対象と資料構造
変数をprivateと定義するのは、変数に依存しないようにするためです.
しかし、多くのプログラマーは当然クエリー(get)、設定(set)関数を公開しますか?
データ抽象
実装を隠すためには抽象化が必要である.
変数間に関数と呼ばれる階層を配置しても、実装は非表示になりません.
クエリー/設定関数を使用して変数を処理しても非表示になりません.さらに重要なのは、実装を知らずにドキュメントのコアを操作できる抽象的なインタフェースを提供することです.これこそ本当の意味でのクラスです.
これは、インタフェースまたはクエリー/設定関数のみを使用して抽象化できるという意味ではありません.開発者は、オブジェクトに含まれる材料をどのように表すかを考慮する必要があります.クエリー/関数の設定方法は望ましくありません.
// 구체적인 Point 클래스
// 명확하게 직교 좌표계를 쓴다는 것을 알 수 있다.
public class Point {
	public double x;
	public double y;
}
// 추상적인 Point 클래스
// 클래스 메서드가 접근 정책을 강제한다.
// = 값을 읽을때 get으로 값을 개별적으로 읽어야 한다. 하지만 좌표를 설정할 때는 두 값을 한꺼번에 설정해야한다.
// 이게 더 좋음

public interface Point {
	double getX(); 
	double getY(); // 조회는 각각 가능하지만
	void setCartesian(double x, double y); // 설정을 2개의 값을 동시에 넣어주어야 한다.
	double getR();
	double getTheta();
	void setPolar(double r, double theta);
追加内容:インタフェースは抽象メソッドの集合+静的メソッド+defaultメソッド+定数
インタフェースには、制御者と指定者が省略されています.
たとえば、double getr()は実際にはpublic abstract double getr()です.
コードには表示されませんが、int num=1の場合.宣伝するなら
これはpublic static final int num=1の定数を意味する.
// 구체적인 Vehicle 클래스
// 변수를 그대로 리턴하는 함수일 것이 틀림없다.
public interface Vehicle {
	public getFuelThankCapacityInGallons();
	public getGallonsOfGasoline();
}
// 추상적인 Vehicle 클래스
// 백분율이라는 추상적인 개념으로 반환하기에 어디서 오는지 사용자에게 드러나지 않는다.
// 이게 더 좋음
public interface Vehicle {
	double getPercentFuelRemaining();
}
資料/オブジェクト非対称
データとデータ構造は本質的に逆である.
  • オブジェクトは
  • 関数のみを公開し、抽象的な後ろにデータを隠す
  • 資料構造は、資料をそのまま公開し、関数を提供しない.
  • この2つの定義は本質的に逆である.実際、この2つの概念は正反対です.微細な違いがあるように見えますが、違いがもたらす影響は大きいです.
    プログラムグラフィックス
    public class Square { 
      public Point topLeft; 
      public double side;
    }
    
    public class Rectangle { 
      public Point topLeft; 
      public double height; 
      public double width;
    }
    
    public class Circle { 
      public Point center; 
      public double radius;
    }
    
    public class Geometry {
      public final double PI = 3.141592653589793;
      
      public double area(Object shape) throws NoSuchShapeException {
        if (shape instanceof Square) { 
          Square s = (Square)shape; 
          return s.side * s.side;
        } else if (shape instanceof Rectangle) { 
          Rectangle r = (Rectangle)shape; 
          return r.height * r.width;
        } else if (shape instanceof Circle) {
          Circle c = (Circle)shape;
          return PI * c.radius * c.radius; 
        }
        throw new NoSuchShapeException(); 
      }
    }
    Geometryクラスに周長を計算する周長()関数を追加する場合は、次の操作を行います.グラフィッククラスは影響を受けません!グラフィッククラスに依存する他のクラスもそうです!逆に、新しいグラフィックを追加する場合は、Geometryクラスに属するすべての関数を修復する必要があります.したがって,二つの条件は全く逆といえる.
    オブジェクト向けグラフィックス/ファセットグラフィックス
    public class Square implements Shape { 
    	private Point topLeft;
    	private double side;
    
    	public double area() { 
    		return side * side;
    	} 
    }
    
    public class Rectangle implements Shape { 
    	private Point topLeft;
    	private double height;
    	private double width;
    
    	public double area() { 
    		return height * width;
    	} 
    }
    
    public class Circle implements Shape { 
    	private Point center;
    	private double radius;
    	public final double PI = 3.141592653589793;
    
    	public double area() {
    		return PI * radius * radius;
    	} 
    }
    オブジェクト向けのグラフィッククラス.各グラフィックオブジェクトのarea()は、多形メソッドを提供します.新しいグラフィックを追加しても、既存の関数には影響しません.新しい関数を追加する場合は、すべてのグラフィッククラスを修復する必要があります.
    前述したように、2つの方法は実際には逆です!したがって,オブジェクトとデータ構造は根本的に分離されている.
    プログラムコードは、既存のデータ構造を変更することなく、新しい関数を簡単に追加できます.逆に、オブジェクト向けのコードは、既存の関数を変更せずに新しいクラスを追加しやすい.
    反対側も本当です.
    プログラムコードに新しい資料構造を追加するのは難しい.そのためには、すべての関数を変更する必要があります.オブジェクト向けコードでは、新しい関数を追加するのは難しいです.これを行うには、すべてのクラスを変更する必要があります.
    複雑なシステムを作成する場合は、新しい関数が必要になるか、新しいデータ型が必要になる可能性があります.クラスとオブジェクト向けのテクノロジーを使用するか、適切なプログラムコードと材料構造を使用して、この状況に適応することをお勧めします.単純なデータ構造やプログラムコードが最適な場合がある.
    ディミットの法則
    Demeter法則はよく知られている人間的な啓発(経験に基づいて問題を解決、学習、または発見する方法)であり、モジュールが自分の操作対象の真実を知らないことを要求している.すなわち,オブジェクトは,材料と公開関数を隠すことによってクエリ関数として内部構造を公開することはできない.
    Demeter法則では「Cクラスのメソッドfは以下のオブジェクトのメソッドのみを呼び出すことができる」としている.
  • 類C
  • fによって作成するオブジェクト
  • f買収対象
  • Cインスタンス変数に格納オブジェクト
  • ただし、上記のオブジェクトが許可するメソッドによって返されるオブジェクトのメソッドを呼び出すことはできません.言い換えれば、見知らぬ人が警戒し、友达と一緒に驚くだけであることを意味します.
    列車が衝突する
    以下のパラメータ法則に違反するコードを列車衝突事故と呼ぶ.getOptions()関数は、返されたオブジェクトのgetScratchDir()関数を呼び出し、getScratchDir()関数は返されたオブジェクトのgetAbsoluthpath()関数を呼び出します.
    final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
    複数の車両は次から次へと列車のように見えます.これは一般的に粗いと考えられている方法で、避けたほうがいいです.上記のコードは、次のように分類されます.
    Options opts = ctxt.getOptions();
    File scratchDir = opts.getScratchDir();
    final String outputDir = scratchDir.getAbsolutePath();
    上記の例がメトリック・ルールに違反しているかどうかは、上記の変数(ctxt、Options、ScratchDir)がオブジェクトであるかデータ構造であるかによって異なります.オブジェクトは内部構造を非表示にする必要があるため、メトリック・ルールに違反しています.しかし、データ構造であれば、内部構造が露出しているため、問題はありません.
    ヘテロ構造
    クエリー関数を使用すると、上記の例ではオブジェクトやデータ構造が混乱します.
    final String outputDir = ctxt.oprions.scratchDir.absolutePath;
    このようなコードであれば、データ構造であるため、これは明らかに問題ではありません.
    このような混乱により,半分がオブジェクトであり,半分がデータ構造の雑種構造である場合がある.ヘテロ構造は、重要な機能を実行する関数であってもよいし、公開変数であってもよいし、公開get/set関数であってもよい.この構造では,新しい関数と新しいデータ構造を追加することは困難である.どちらの世界にも欠点が集中した構造.そのため、このような構造はできるだけ避けます.これは、プログラマーが関数やタイプを保護するか、公開するか(さらに悪いことに、彼らは知らない)分からないため、設計が不適切です.
    構造体を隠す
    上記のoutputDirの例では、これは良い方法ではありません.同じモジュールでなぜこのパスが必要なのか(しばらく下に進むと)、このようなコードがあることに気づきました.
    String outFile = outputDir + "/" + className.replace('.', '/') + ".class"; 
    FileOutputStream fout = new FileOutputStream(outFile); 
    BufferedOutputStream bos = new BufferedOutputStream(fout);
    抽象化のレベルを混同して、多少不便です.ポイント、スラッシュ、ファイル拡張子、およびファイルオブジェクトを任意にブレンドすることはできません.または、上記のコードは、パスを取得するために一時ファイルを作成する理由を示しています.
    では、ctxtオブジェクトに一時ファイルを作成させたらどうなりますか?
    BufferedOutputStream bos = ctxt.createScratchFileStream(classFileName);
    これはオブジェクトを保存するのに適したタスクのように見えます!ctxtは内部構造を表示せず、モジュールは理解する必要のない複数のオブジェクトをナビゲートする必要はありません.だからディミットの法則に違反しない.
    客体なら、何かをするべきで、中身を出すことはできません!
    データ転送オブジェクト
    資料構造体の典型的な形式は,公開変数のみで関数のないクラスである.データ転送対象データ転送対象、DTOと呼ばれる場合がある.
    その他:springでは、サービス、CoデータをコントローラとRepositoryの各レイヤ間で転送および交換する際に使用されるクラスと見なすこともできます.
    public class Address { 
      public String street; 
      public String streetExtra; 
      public String city; 
      public String state; 
      public String zip;
    }
    アクティビティレコード
    DTOの特殊な形式.公開変数または専用変数を有するデータ構造にはgetter/setterが含まれるが、カニsaveやfindなどのナビゲーション関数も提供される.アクティビティレコードは、データベース・テーブルまたは他のソースから直接データを変換した結果です.
    残念なことに、開発者は通常、ビジネス・ルール・メソッドをアクティビティ・レコードに追加し、オブジェクトと見なします.しかし、このようにすると、雑種構造が現れる.
    解決策は、アクティブなレコードをデータ構造と見なすことです.ビジネス・ルールが含まれ、内部資料が非表示のオブジェクトを個別に作成する必要があります.(ここでは、内部資料はアクティブレコードの例である可能性が高い.)
    n/a.結論
    オブジェクトは、アクションを公開し、材料を非表示にします.したがって、既存のアクションを変更せずに新しいオブジェクトタイプを追加するのは簡単ですが、既存のオブジェクトに新しいアクションを追加するのは難しいです.
    オブジェクト向けコードは新しいクラスを追加しやすいが、新しい関数を追加するのは難しい.
    データ構造は何もしないで、データを公開することができます.したがって,既存のデータ構造に新しい動作を追加することは容易であるが,既存の関数に新しいデータ構造を追加することは困難である.
    (一部)システムを実装する際に、新しいデータ型の柔軟性を高める必要がある場合は、オブジェクトがより適切になります.他の状況で柔軟に新しいアクションを追加する必要がある場合は、データ構造とプログラムコードの使用に適しています.優れたソフトウェア開発者は、この事実を理解し、直面している問題に対して最適なソリューションを選択する必要があります.