パラメトリック法則と関数体化


1.関数フィルタ


関数フィルタは、前置関数の結果に関数を呼び出す必要がある場合に、関数を接続して呼び出すテクニックです.
using System;
					
public class Program
{
	public class Name
	{
		public string FirstName { private set; get; }
		public string LastName { private set; get; }
		
		public Name(string inFirstName, string inLastName)
		{
			FirstName = inFirstName;
			LastName = inLastName;
		}
		
		public Name ChangeFirstName(string inFirstName)
		{
			FirstName = inFirstName;
			return this;
		}
		
		public Name ChangeLastName(string inLastName)
		{
			LastName = inLastName;
			return this;
		}
		
		public new string ToString()
		{
			return $"{FirstName} {LastName}";
		}
	}
	
	public static void Main()
	{
		Name myName = new Name("David", "Cho");
		Console.WriteLine(myName.ToString());
		
		myName.ChangeFirstName("Paul").ChangeLastName("Kim");
		Console.WriteLine(myName.ToString());
	}
}
上記のコードでは、39行目にmyNameChangeFirstNameという関数が呼び出されます.
各関数は、その結果の別の関数を呼び出すために、所属するオブジェクトインスタンスを返します.
このような接続関数呼び出しの方式を関数体生成と呼ぶ.
上記の例では、オブジェクトが関数フィルタを使用してステータス(内部データ)を変更しています.もう1つは,C#でより一般的でより有用な関数体生成である.LINQです
using System;
using System.Linq;
					
public class Program
{	
	public static void Main()
	{
		int[] data = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
		
		var filteredArray = data.Where(v => v > 5).OrderByDescending(v => v).ToArray();
		
		Console.WriteLine(filteredArray.Select(v => v.ToString()).Aggregate((a, b) => $"{a}, {b}"));
		Console.WriteLine(data.Select(v => v.ToString()).Aggregate((a, b) => $"{a}, {b}"));
	}
}
10行目では、LINQ関数を入力してChangeLastName配列を変更します.dataオブジェクトの関数フィルタリングの例では、各関数が返すオブジェクトは同じです.自分が所属しているオブジェクトのインスタンスを返します.
しかし、LINQの例では、各サブディビジョン関数の戻り値はそれぞれ異なる.Name関数の場合、データは同じ可数タイプのWrappingのみで、異なるインスタンスを返します.Where関数でも、戻りタイプ自体が異なります.
出力ToArrayおよびfilteredArrayは、異なる結果を見ることができる.
このような利点は、フィルタされた関数に沿ってデータストリームを読み取ることができることである.
読みやすさがよくなる
オブジェクトをターゲットとして使用する場合は、浮動小数点効果と関数の呼び出し時間の制限に注意してください.
LINQのようなフィルタ関数が元のデータを変更しない場合は、あまり気にする必要はありません.

2.離散法則と関数体生成


CLINCODEは以下のパラメータ法則を紹介した.
ディミット法則は,モジュールが自分で操作する対象の詳細を知らない法則である.
より正確には、クラスCのメソッドfは、以下のオブジェクトのメソッドのみを呼び出すことができる.
  • 類C
  • fによって作成するオブジェクト
  • f買収対象
  • Cインスタンス変数に格納オブジェクト
  • このルールに従うと、他のオブジェクトの内部データや実装を事前に理解する必要がなくなり、マージが低減され、柔軟性が向上し、変更が簡素化されます.
    結合度が低いほど、クラスの責任範囲が明確になり、他の環境で再使用するのも便利になります.
    ではDemeter法則と関数フィルタリングはどのような関係があるのでしょうか.
    通常、エンコード時に設計基準を最大限に遵守している場合、関数フィルタを使用すると、設計基準に違反することがあります.
    次のようになります.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    					
    public class Program
    {
    	public class Library
    	{
    		private List<User> _members;
    		private List<Book> _books;
    		
    		public Library()
    		{
    			_members = new List<User>();
    			_books = new List<Book>();
    		}
    		
    		public User GetMemberById(int inId)
    		{
    			if (_members.Any(v => v.Id == inId))
    			{
    				return _members.FirstOrDefault(v => v.Id == inId);
    			}
    			return User.None;
    		}
    	}
    	
    	public class User
    	{
    		public static readonly User None = new User(0, string.Empty);
    		public int Id { private set; get; }
    		public string Name { private set; get; }
    		private Book _rentBook;
    		
    		public User(int inId, string inName)
    		{
    			Id = inId;
    			Name = inName;
    		}
    			
    		public Book GetRentBook()
    		{
    			return _rentBook ?? Book.None;
    		}
    	}
    		
    	public class Book
    	{
    		public static readonly Book None = new Book(string.Empty, string.Empty);
    		public string Name { private set; get; }
    		public string Author { private set; get; }
    		
    		public Book(string inName, string inAuthor)
    		{
    			Name = inName;
    			Author = inAuthor;
    		}
    		
    		public string GetBookInfo()
    		{
    			if (this.Equals(Book.None)) return string.Empty;
    			return $"{Name} by {Author}";
    		}
    		
    		public new bool Equals(object inBook)
    		{
    			if (inBook == null) return false;
    			if (Object.ReferenceEquals(this, inBook)) return true;
    			
    			if (inBook.GetType() != typeof(Book)) return false;
    			
    			Book target = inBook as Book;
    			if (!Name.Equals(target.Name) || !Author.Equals(target.Author)) return false;
    			return true;
    		}
    	}
    	
    	public static void Main()
    	{
    		Library myLibrary = new Library();	
    		Console.WriteLine(myLibrary.GetMemberById(1).GetRentBook().GetBookInfo());
    	}
    }
    以上のコードは図書館1番会員が本の貸し出し情報を調べるプログラムです.
    モジュールは、dataオブジェクトを含む.
    次に81行で、Libraryオブジェクトから1番のメンバーをクエリーし、そのメンバーが借りた本をクエリーします.その後、本の情報を返し、画面に出力します.
    一見,関数フィルタリングにより論理をスムーズに読み取ることができるが,このコードはパラメータ法則に違反している.
    現在のモジュールで既知のオブジェクト情報はmyLibraryのみです.
    したがって、Demeterの法則に従って、このモジュールはLibraryの関数を呼び出すだけです.
    まず、Library関数はGetMemberById類の関数であるため、公制法則に違反しない.
    しかし、Library関数はGetRentBook類の関数であるため、公法に違反している.
    また,User関数はGetBookInfoクラスの関数であるため,公制法則に違反している.BookオブジェクトとUserオブジェクトは、2番目の公定法則とBookに属していないと思います.
    ただし、ここで強調したいのは、上記の関数として受信したオブジェクトが「作成」オブジェクトではなく「参照」オブジェクトであることです.
    fで「作成」されたオブジェクトはfでのみ使用され、fが終了すると消えます.
    ただし、リファレンスオブジェクトは別のメモリに存在し、変更が発生すると、他のオブジェクトのステータスが変更され、負の効果が発生する可能性があります.
    上記の例はGetterのみが参照する論理であるため、付随的な効果はありませんが、フィルタを使用してステータスを変更する関数を呼び出すと、どのオブジェクトに何をしたのか分かりにくいです.
    したがって,この場合,CLINコードは2つの解決策を提供する.

    1.コンテキストで関数を使用

    using System;
    using System.Collections.Generic;
    using System.Linq;
    					
    public class Program
    {
    	public class Library
    	{
    		private List<User> _members;
    		private List<Book> _books;
    		
    		public Library()
    		{
    			_members = new List<User>();
    			_books = new List<Book>();
    		}
    		
    		public User GetMemberById(int inId)
    		{
    			if (_members.Any(v => v.Id == inId))
    			{
    				return _members.FirstOrDefault(v => v.Id == inId);
    			}
    			return User.None;
    		}
    		
    		public string GetUsersRentedBookInfo(int inId)
    		{
    			User member = GetMemberById(inId);
    			return member.GetRentBookInfo();
    		}
    	}
    	
    	public class User
    	{
    		public static readonly User None = new User(0, string.Empty);
    		public int Id { private set; get; }
    		public string Name { private set; get; }
    		private Book _rentBook;
    		
    		public User(int inId, string inName)
    		{
    			Id = inId;
    			Name = inName;
    		}
    			
    		public Book GetRentBook()
    		{
    			return _rentBook ?? Book.None;
    		}
    		
    		public string GetRentBookInfo()
    		{
    			return GetRentBook().GetBookInfo();
    		}
    	}
    		
    	public class Book
    	{
    		public static readonly Book None = new Book(string.Empty, string.Empty);
    		public string Name { private set; get; }
    		public string Author { private set; get; }
    		
    		public Book(string inName, string inAuthor)
    		{
    			Name = inName;
    			Author = inAuthor;
    		}
    		
    		public string GetBookInfo()
    		{
    			if (this.Equals(Book.None)) return string.Empty;
    			return $"{Name} by {Author}";
    		}
    		
    		public new bool Equals(object inBook)
    		{
    			if (inBook == null) return false;
    			if (Object.ReferenceEquals(this, inBook)) return true;
    			
    			if (inBook.GetType() != typeof(Book)) return false;
    			
    			Book target = inBook as Book;
    			if (!Name.Equals(target.Name) || !Author.Equals(target.Author)) return false;
    			return true;
    		}
    	}
    	
    	public static void Main()
    	{
    		Library myLibrary = new Library();	
    		
    		Console.WriteLine(myLibrary.GetMemberById(1).GetRentBook().GetBookInfo());
    		Console.WriteLine(myLibrary.GetUsersRentedBookInfo(1));
    	}
    }
    93行は、関数フィルタリングによってf 가 생성한 객체オブジェクトおよびUserオブジェクトを外部に露出する.
    人によって考え方は異なるかもしれませんが、文脈から見ると、1番プレイヤーが借りた本の情報を検索する動作は責任と見なすことができます.もしそうであれば、この操作を1つの関数に組み合わせることができます.
    したがって、94行でBookという関数を作成して呼び出します.
    メトリック法則を満たすため、コードは、GetUserRentedBookInfoオブジェクトおよびUserオブジェクトのAPI(メソッドフラグ)を知る必要がなくなります.そのため結合度も低下した.

    二、公制の法則は客体に適用される法則である。いっそ資料構造に変えましょう。


    オブジェクトは、データを非表示にし、動作のみを公開するため、安定性と柔軟性を確保するための努力の一部です.
    このクラスでは、データ構造がデータを公開し、データの目的と用途を示すことを意図している.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    					
    public class Program
    {
    	public class Library
    	{
    		private List<User> _members;
    		private List<Book> _books;
    		
    		public Library()
    		{
    			_members = new List<User>();
    			_books = new List<Book>();
    		}
    		
    		public User GetMemberById(int inId)
    		{
    			if (_members.Any(v => v.Id == inId))
    			{
    				return _members.FirstOrDefault(v => v.Id == inId);
    			}
    			return User.None;
    		}
    	}
    	
    	public struct User
    	{
    		public static readonly User None = new User {
    			Id = 0, 
    			Name = string.Empty,
    			RentBook = Book.None
    		};
    		
    		public int Id = 0;
    		public string Name = "";
    		public Book RentBook = Book.None;
    	}
    		
    	public struct Book
    	{
    		public static readonly Book None = new Book(string.Empty, string.Empty);
    		public string Name = "";
    		public string Author = "";
    		
    		public Book(string inName, string inAuthor)
    		{
    			Name = inName;
    			Author = inAuthor;
    		}
    		
    		public bool Equals(Book inBook)
    		{
    			return Name.Equals(inBook.Name) && Author.Equals(inBook.Author);
    		}
    	}
    	
    	public static void Main()
    	{
    		Library myLibrary = new Library();	
    		Console.WriteLine($"{myLibrary.GetMemberById(1).RentBook.Name} by {myLibrary.GetMemberById(1).RentBook.Author}");
    	}
    }
    クラスBookおよびUserは構造体となる.
    開示されたデータ構造BookおよびUserは、デフォルトでは開示情報であるため、パラメータ法則を使用する必要はない.
    また、公開されたデータも参照ではなくコピーされるため、負の影響はありません.

    3.整理


    関数フィルタは、サブディビジョンされた部品関数を組み合わせることで可読性を向上させ、論理フローをより理解しやすくする技術です.
    しかし,関数体生成を盲目的に用いると,知らず知らずのうちにオブジェクトの結合度が向上する可能性がある.
    最初に公開されたデータのデータ構造を関数フィルタリングする場合,パラメータ法則を考慮する必要はない.また、フィルタされた関数が元のタイプのデータを返すと、これらの値はコピーされ続け、伝播時に付随する影響を心配する必要はありません.
    ただし、オブジェクト向けの関数体を使用して生成する場合は注意してください.
    パラメータの法則に違反しないように、各関数をよくチェックする必要があります.
    また、関数がパラメータ法則に違反している場合は、その関数を呼び出す責任が元のオブジェクトに返されることを確認する必要があります.
    1つまたは2つの関数だけを1つの法則で計算するのは、あまりにもひどいかもしれません.
    ルールを守るために作成された関数名が長すぎて、気に入らないかもしれません.
    しかし,このような例外が次から次へと蓄積されると,より扱いにくい問題が生じる可能性がある.
    だから小さな改善でも、今すぐに変えられるようになれば、少しずつ変わっていく気持ちで開発していきます.
    少なくとも退勤時には、出勤よりもきれいなコードを残すために.
    キャンプ場は初めて来た時よりきれいで、それから離れます.