最近、Scala開発者のコミュニティでは、 Typeクラスのデザインパターンにますます注目が集まっています。 不要な依存関係に対処すると同時に、コードをクリーンにするのに役立ちます。 以下に、その使用方法とこのアプローチの利点を例を挙げて示します。
この記事は、ScalaだけでなくJavaで作成するプログラマー向けに設計されています。少なくとも理論的には、コンポーネントが相互接続されておらず、作成後に拡張可能な多くの応用問題の解決策のようです。 他の言語の開発者や設計者にとっても興味深いかもしれません。
背景
クラスがこのクラスに関連付けられた動作を追加する必要があるときに誰もが問題に直面しました:比較、シリアル化、シリアル化からの読み取り、クラスのインスタンス化、レンダリング、これは非常に便利なライブラリが必要とする別の動作かもしれません。
Javaの世界でこれらの問題を解決するには、いくつかの方法があります。
- インターフェイスを作成し、クラス自体に実装します(たとえば、Comparable)。
- クラスオブジェクトを受け取り、必要なアクションを実行する特定のインスタンス(Comparatorなど)を個別のインターフェイスに記述します。
- 個々のタスクのための特別な方法もあります:
- オブジェクト作成用-ファクトリーテンプレート、
- サードパーティライブラリとのリンクにはアダプターが使用されます。
- シリアル化と読み取りには、時々Reflectionを使用します。 これは最近の投稿の剖検を連想させますが、腹部手術も必要になることがあります。すぐに予約します。タイプクラスはそれを置き換えることができません。
したがって、本質的には、2つの選択肢があります。サードパーティの機能をクラスに密接に関連付けるか、別のオブジェクトで機能を取り出して、その転送を処理します。 アプリケーションのユーザーを例に取りましょう。 これは、ユーザーを比較する機能を追加するように見える場合があります。
// , def sort[T <: Comparable[T]](elements: Seq[T]): Seq[T] // class Person(val id: Id[Person], val name: String) extends Comparable[Person] { def compareTo(Person anotherPerson): Int = { return this.name.compareTo(anotherPerson.name) } } // sort(persons)
ここで、ソートの特定のアプリケーションが非常に明確に見えるのは良いことです。 ただし、比較ロジックはクラスと密接に関連しています(たとえば、XMLを生成してデータベースに書き込むライブラリにモデルオブジェクトが依存することを好む人はいないでしょう)。 さらに、別の問題が発生します。複数の比較メソッドを定義することは不可能です。つまり、明日、プログラムの他の場所でidでユーザーを比較する場合、成功せず、プライベートクラスの比較メソッドを書き換えることができません。
Javaには、この目的のためのComparatorクラスがあり、柔軟性を高めることができます。
// , def sort[T](elements: Seq[T], comparator: Comparator[T]): Seq[T] // class Person(val id: Id[Person], val name: String) trait Comparator[T] { def compare(object1: T, object2 T): Int } class PersonNameComparator extends Comparator[Person] { def compare(onePerson: Person, anotherPerson: Person): Int = { return onePerson.name.compareTo(anotherPerson.name) } } // val nameComparator = new PersonNameComparator() sort(persons, nameComparator)
これで、いくつかの比較メソッドを定義できるようになり、比較ロジックはモデルクラスに関連付けられなくなりました。 また、ソートアルゴリズムのクラスと定義が閉じられている場合でも、独自のコンパレータを記述することを妨げるものはありません。 ソートの呼び出しがやや複雑になったことは注目に値します。
工場とアダプターの使用は、ライフサイクルの同様の管理とインスタンスの転送を意味します。 それでも、一般に1つの標準タスクについて、これらすべてのクールな名前を覚えておく必要があります。
そして、タイプクラスが表示されます
ここで、暗黙的にパラメーターを渡すScalaの機能が役立ちます。 前の例を基礎としてみましょうが、アルゴリズムの定義を変えて、Comparatorを暗黙的に渡します。
def sort[T](elements: Seq[T])(implicit comparator: Comparator[T]): Seq[T]
つまり、スコープに適切なComparatorがあり、希望するtypeパラメーターの値がある場合、プログラマーの手間をかけることなく、コンパイラーによって自動的にメソッドに置き換えられます。 そのため、適切なコンパレータをスコープに入れます。
implicit val personNameComparator = Comparator[Person] { def compare(onePerson: Person, anotherPerson: Person): Int = { return onePerson.name.compareTo(anotherPerson.name) } }
implicitキーワードは 、値が置換で使用されることを保証します。 プログラムのプロセスで各タイプのインスタンスが1つだけ作成されるため、暗黙的な実装はステートレスでなければならないことに注意することが重要です。
これで、Comparableの実装を使用して、元のバージョンと同じ方法で並べ替えを呼び出すことができます。
// sort(persons)
これは、比較メソッド自体がモデルオブジェクトとはまったく関係がないという事実にもかかわらずです。 任意の比較メソッドをスコープに配置でき、それらはオブジェクトに使用されます。
型パラメーターを自分で入力する場合は、もう少し興味深いオプションが発生します。 つまり、 Map [Id [Person]、List [Permission]]には、 MapJsonSerializer 、 IdJsonSerializer 、 ListJsonSerializer 、およびPermissionJsonSerializerが必要です。 これらは 、 毎回書き込むアナログのPersonPermissionsMapJsonSerializerではなく、任意の順序で再利用できます。 この場合、暗黙のオブジェクトを決定する方法はわずかに異なります。オブジェクトはありませんが、関数があります。
implicit def ListComparator[V](implicit comparator: Comparator[V]) = new Comparator[List[V]] { def compare(oneList: List[V], anotherList: List[V]): Int = { for((one, another) <- oneList.zip(anotherList)) { val elementsCompared = comparator,compare(one, another) if(elementsCompared > 0) return 1 else if(elementsCompared < 0) return -1 } return 0 } }
これがメソッド全体です。 最良の部分は、クラスとオブジェクトの対応をどこにも保存せずに、あらゆる種類のJSONParsers、XMLSerializersをPersonFactoryと一緒に取得できることです。Scalaコンパイラがすべてを行います。
TCSでは、このメソッドを使用して、たとえばモデルのクラスで例外をラップします。 型クラスを使用すると、スローされたブロックをラップする型の例外インスタンスを作成できます。 これを従来の方法で行った場合、例外ファクトリを作成して転送する必要があるため、古い方法で手動で例外をスローする方が簡単です。 今、すべてが美しいです。
次は?
実際、Typeクラスのトピックはここで終わりません。続きとして、 ScalaのビデオTypeclassesをお勧めします。
この記事では、 この問題についてさらに根本的に説明します。