パラメトリック法則と関数体化
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行目にmyName
とChangeFirstName
という関数が呼び出されます.
各関数は、その結果の別の関数を呼び出すために、所属するオブジェクトインスタンスを返します.
このような接続関数呼び出しの方式を関数体生成と呼ぶ.
上記の例では、オブジェクトが関数フィルタを使用してステータス(内部データ)を変更しています.もう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は、以下のオブジェクトのメソッドのみを呼び出すことができる.
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());
}
}
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}"));
}
}
CLINCODEは以下のパラメータ法則を紹介した.
ディミット法則は,モジュールが自分で操作する対象の詳細を知らない法則である.
より正確には、クラスCのメソッドfは、以下のオブジェクトのメソッドのみを呼び出すことができる.
結合度が低いほど、クラスの責任範囲が明確になり、他の環境で再使用するのも便利になります.
では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つの法則で計算するのは、あまりにもひどいかもしれません.
ルールを守るために作成された関数名が長すぎて、気に入らないかもしれません.
しかし,このような例外が次から次へと蓄積されると,より扱いにくい問題が生じる可能性がある.
だから小さな改善でも、今すぐに変えられるようになれば、少しずつ変わっていく気持ちで開発していきます.
少なくとも退勤時には、出勤よりもきれいなコードを残すために.
キャンプ場は初めて来た時よりきれいで、それから離れます.
Reference
この問題について(パラメトリック法則と関数体化), 我々は、より多くの情報をここで見つけました
https://velog.io/@hgcho84/디미터-법칙과-함수-체이닝
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
Reference
この問題について(パラメトリック法則と関数体化), 我々は、より多くの情報をここで見つけました https://velog.io/@hgcho84/디미터-법칙과-함수-체이닝テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol