オブジェクト指向プログラミング言語では、ミックスイン(またはミックスイン)[ 1 ] [ 2 ] [ 3 ] [ 4 ]は、他のクラスの親クラスである必要はなく、他のクラスが使用できるメソッドを含むクラスです。他のクラスがミックスインのメソッドにアクセスする方法は、言語によって異なります。ミックスインは、「継承」ではなく「インクルード」されるものとして説明されることもあります。
ミックスインはコードの再利用を促進し、多重継承が引き起こす可能性のある継承の曖昧さ(「ダイヤモンド問題」) [ 5 ]を回避したり、言語の多重継承のサポート不足を回避したりするために使用できます。ミックスインは、実装されたメソッドを持つインターフェースと見なすこともできます。このパターンは、依存性逆転の原則を強制する例です。
Mixins は、 Symbolicsのオブジェクト指向Flavorsシステム (Howard Cannon によって開発)で初めて登場しました。これはLisp Machine Lispで使用されたオブジェクト指向のアプローチです。その名前は、マサチューセッツ州サマービルのSteve's Ice Cream Parlorに由来しています。 [ 1 ]アイスクリーム店のオーナーは、基本的なフレーバーのアイスクリーム (バニラ、チョコレートなど) に、追加のアイテム (ナッツ、クッキー、ファッジなど) を組み合わせて提供し、そのアイテムを「mix-in」と呼んでいました。これは当時、彼自身の商標登録された用語でした。[ 2 ]
ミックスインは、プログラマがクラスにコードを挿入できるようにする言語概念です。ミックスインプログラミングは、機能の単位をクラス内に作成し、他のクラスとミックスインするソフトウェア開発スタイルです。 [ 6 ]
ミックスインクラスは、必要な機能を含む親クラスとして機能します。サブクラスは、この機能を継承したり、単に再利用したりできますが、特殊化の手段としては利用できません。通常、ミックスインは、厳密な単一の「is a」関係を作成することなく、必要な機能を子クラスにエクスポートします。ここに、ミックスインと継承の概念の重要な違いがあります。子クラスは親クラスのすべての機能を継承できますが、子が親の「種類」であるという意味論を必ずしも適用する必要はありません。
Simulaでは、クラスは属性、メソッド、クラスの初期化がすべてまとめて定義されるブロック内で定義されます。そのため、クラスに対して呼び出すことができるすべてのメソッドがまとめて定義され、クラスの定義が完了します。
Flavorsにおいて、ミックスインとは、別のクラスがスロット定義とメソッドを継承できるクラスのことです。ミックスインは通常、直接インスタンスを持ちません。Flavor は複数の他の Flavor から継承できるため、1 つ以上のミックスインから継承できます。なお、オリジナルの Flavors ではジェネリック関数は使用されていませんでした。
New Flavors(Flavorsの後継)およびCLOSでは、メソッドは「汎用関数」として整理されています。これらの汎用関数は、クラスディスパッチとメソッドの組み合わせによって、複数のケース(メソッド)で定義される関数です。
CLOS と Flavors では、ミックスイン メソッドを使用して既存のメソッドに動作を追加できます。Flavorsでは、:beforeデーモン:after、ワッパー、ラッパーなどを使用できます。CLOS では、:aroundメソッドと、を介してシャドウ メソッドを呼び出す機能が追加されましたCALL-NEXT-METHOD。たとえば、stream-lock-mixin を使用すると、ストリーム クラスの既存のメソッドの周りにロックを追加できます。Flavors では、ラッパーまたはワッパーを記述し、CLOS では:aroundメソッドを使用します。CLOS と Flavors の両方で、メソッドの組み合わせを介して計算された再利用が可能です。:before、:afterおよび:aroundメソッドは、標準メソッドの組み合わせの機能です。他のメソッドの組み合わせも提供されています。
一例として+、メソッドの組み合わせがあります。これは、汎用関数の適用可能な各メソッドの結果を算術的に加算して戻り値を計算します。これは、例えば、グラフィカルオブジェクトのボーダーミックスインで使用されます。グラフィカルオブジェクトは、汎用的な幅関数を持つことができます。ボーダーミックスインはオブジェクトの周囲に境界線を追加し、その幅を計算するメソッドを持っています。新しいクラスbordered-button(グラフィカルオブジェクトであり、かつborderミックスインを使用するクラス)は、メソッドの組み合わせを介して、適用可能なすべての幅メソッドを呼び出すことで、その幅を計算します+。すべての戻り値が加算され、オブジェクトの結合された幅が作成されます。
OOPSLA 90 の論文[ 10 ]で、 Gilad Bracha と William Cook は Smalltalk、Beta、CLOS に見られるさまざまな継承メカニズムを、ミックスイン継承の特殊な形式として再解釈しています。
FlavorsとCLOS( Common Lispの一部)以外にも、ミックスインを使用する言語には以下のようなものがあります。
一部のプログラミング言語は言語レベルでミックスインをサポートしていませんが、実行時にあるオブジェクトから別のオブジェクトにメソッドをコピーすることで、ミックスインのメソッドを「借用」し、簡単に模倣することができます。これは静的型付け言語でも可能ですが、拡張されたメソッドセットを持つ新しいオブジェクトを構築する必要があります。
ミックスインをサポートしていない他の言語でも、別の言語構造を介して間接的にミックスインをサポートできます。たとえば、Visual Basic .NETや C# では、インターフェースに拡張メソッドを追加できます。つまり、拡張メソッドが定義されたインターフェースを実装するクラスは、擬似メンバーとして拡張メソッドを利用できるようになります。
Common Lispは、CLOS(Common Lisp Object System)において、フレーバーと同様のミックスインを提供します。
object-widthこれは、メソッドの組み合わせを使用する引数を1つ持つ汎用関数です+。この組み合わせにより、汎用関数に適用可能なすべてのメソッドが呼び出され、結果が加算されます。
( defgeneric object-width ( object ) ( :method-combination + ))buttonこれは、ボタンのテキストを格納するためのスロットが1つあるクラスです。
( defclass button () (( text :initform "click me" )))ボタンクラスのオブジェクトには、ボタンのテキストの長さに基づいて幅を計算するメソッドがあります。+は、同名のメソッドの組み合わせのメソッド修飾子です。
( defmethod object-width + (( object button )) ( * 10 ( length ( slot-value object 'text ))))クラスborder-mixinです。命名は単なる慣例です。スーパークラスもスロットもありません。
( defclass border-mixin () ())境界線の幅を計算する方法があります。ここでは、幅は4です。
( defmethod object-width + (( object border-mixin )) 4 )bordered-buttonborder-mixinは、と両方から継承するクラスですbutton。
( defclass bordered-button ( border-mixin button ) ())これでボタンの幅を計算できます。呼び出すと80 が計算されます。結果は、適用可能な唯一のメソッド、つまりクラスのobject-widthメソッドの結果です。object-widthbutton
? (オブジェクト幅( make-instance 'button )) 80また、a の幅を計算することもできますbordered-button。 を呼び出すとobject-width84 が計算されます。結果は、適用可能な 2 つのメソッドの結果の合計です。object-widthクラスのメソッドとクラスのbuttonメソッドです。object-widthborder-mixin
? (オブジェクト幅( 'bordered-button 'のインスタンスを作成)) 84C++では、ミックスインを実装する従来の方法は、奇妙な繰り返しテンプレートパターン(CRTP) を使用することです。
import std ;using std :: string ; using std :: three_way_comparable ; using std :: tuple ;template < typename Host > concept HasKey = requires ( const Host & x ) { { x . key () } -> three_way_comparable < bool > ; };template < HasKey Host > class CompareByKey { protected : CompareByKey () = default ; ~ CompareByKey () = default ; public : // 可能な場合は強い順序付けを可能にするために推論型を使用しますが、// 必要に応じて他の順序付けも保持します[[ nodiscard ]] friend auto operator <=> ( const Host & lhs , const Host & rhs ) noexcept { return lhs . key () <=> rhs . key (); }[[ nodiscard ]] friend bool operator == ( const Host & lhs , const Host & rhs ) noexcept { return lhs . key () == rhs . key (); } };class Person : public CompareByKey < Person > { private : string name ; int age ; public : Person ( string name , int age ) : name { std :: move ( name )}, age { age } {}[[ nodiscard ]] tuple < const string & , const int &> key () const noexcept { return std :: tie ( name , age ); } };C++26以降、C++ではリフレクション用のアノテーションを使用してミックスインを実装できるようになりました。
import std ;using std :: hash ; using std :: string ; using std :: vector ; using std :: meta :: info ;template < typename Host > struct EqualityMixin { [[ nodiscard ]] bool operator == ( const Host & other ) const noexcept { const Host & self = static_cast < const Host &> ( * this );template for ( constexpr info m : std :: meta :: nonstatic_data_members_of ( ^^ Host )) { if ( ! ( self .[ : m : ] == other .[ : m : ])) { return false ; } } return true ; }[[ nodiscard ]] bool operator != ( const Host & other ) const noexcept { return ! ( * this == other ); } };template < typename Host > struct HashMixin { static constexpr uint64_t FNV_PRIME = 0x9e3779b9 ;[[ nodiscard ]] size_t hash () const noexcept { const Host & self = static_cast < const Host &> ( * this ); size_t h = 0 ; template for ( constexpr info m : std :: meta :: nonstatic_data_members_of ( ^^ Host )) { h ^= hash < [ : std : meta :: type_of ( m ) : ] > {}( self .[ : m : ]) + FNV_PRIME + ( h << 6 ) + ( h >> 2 ); } return h ; } };template < typename Host > struct ZeroMixin { void zero () { Host & self = static_cast < Host &> ( * this ); template for ( constexpr info m : std :: meta :: nonstatic_data_members_of ( ^^ Host )) { self .[ : m : ] = {}; } } };// ミックスイン テンプレートを注釈タグのパラメータとして格納します// パラメータはstd::meta::template_arguments_of()を使用して取得できますtemplate < template < typename > typename ... Ms > struct Mixin {};struct [[ = Mixin < EqualityMixin , HashMixin , ZeroMixin > {}]] Vec2 { double x = 0.0 ; // x座標double y = 0.0 ; // y座標};struct [[ = Mixin < EqualityMixin , HashMixin , ZeroMixin > {}]] Pixel { uint8_t r = 0 ; // ピクセルの赤値uint8_t g = 0 ; // ピクセルの緑値uint8_t b = 0 ; // ピクセルの青値uint8_t a = 255 ; // ピクセルのアルファ値};class [[ = Mixin < HashMixin , ZeroMixin > {}]] Person { private : string name ; int age ; public : Person ( string name , int age ) : name { std :: move ( name )}, age { age } {}// ... };D言語には、文字列ミックスインとテンプレートミックスインの2種類のミックスインがあります。
import std.stdio ;mixinテンプレートAddFunction () { void addedFunction () { writeln ( "mixinから関数を追加しました!" ); } }class MyClass { mixin AddFunction ; // addedFunction() を MyClass に注入します}void main () { // 文字列ミックスインstring code = ` writeln("文字列ミックスインからのこんにちは!"); ` ;mixin ( code ); // これは writeln コードを挿入して実行します// テンプレートミックスインMyClass obj = new MyClass (); obj.addedFunction ( ) ; }Javaでは、インターフェースのデフォルトメソッド実装を使用することで、ミックスインをエミュレートできます。
import java.time.Instant ;interface LoggerMixin { default void log ( String fmt , Object ... args ) { System . out . printf ( "[LOG]: %s" , String . format ( fmt , args )); } }interface TimestampMixin { default String currentTimestamp () { Instant . now (). toString (); } }class MyService implements LoggerMixin , TimestampMixin { public void doWork () { log ( "Starting work at %s" , currentTimestamp ()); } }public class Main { public static void main ( String [] args ) { MyService service = new MyService (); service . doWork (); } }extendアプローチ技術的には、オブジェクト内のキーに関数をバインドすることで、オブジェクトに動作を追加することは可能です。しかし、状態と動作が分離されていないことには、次のような欠点があります。
拡張関数は、次の動作を混合するために使用されます(TypeScript の型注釈付き):[ 19 ]
interface NameMixin { fullName () : string ; rename ( first : string , last : string ) : this ; }class Halfling { firstName : string ; lastName : string ;constructor ( first : string , last : string ) { this.firstName = first ; this.lastName = last ; } }const mixin : NameMixin = { fullName ( ) { return this.firstName + ' ' + this.lastName ; } , rename ( first : string , last : string ) { this.firstName = first ; this.lastName = last ; return this ; } } ;// extend関数function extend < T , U > ( obj : T , mixin : U ) : T & U { Object . keys ( mixin ). forEach ( key => { ( obj as any )[ key ] = ( mixin as any )[ key ]; }); return obj as T & U ; }// 他のメソッドをミックスインするextend ( Halfling . prototype , mixin );// Halfling にミックスイン メソッドが含まれるようになったことを示す: interface Halfling extends NameMixin {}const sam : Halfling = new Halfling ( 'Sam' , 'Loawry' ); const frodo : Halfling = new Halfling ( 'Freeda' , 'Baggs' );console.log ( sam.fullName ( ) ); // Sam Loawry console.log ( frodo.fullName ( ) ) ; // Freeda Baggssam.rename ( ' Samwise ' , 'Gamgee' ); frodo.rename ( ' Frodo' , 'Baggins' ) ;console.log ( sam.fullName ( )); // サムワイズ・ギャムジーconsole.log ( frodo.fullName ( ) ) ; //フロド・バギンズObject.assign()interface Person { name : string ; city : string ; born : string ; }interface Mix1 { toString () : string ; age () : number ; }interface Mix2 { toString () : string ; }type PersonWithMixins = Person & Mix1 & Mix2 ;// オブジェクトの作成const obj1 : Person = { name : 'Marcus Aurelius' , city : 'Rome' , born : '121-04-26' };// Mixin 1 const mix1 : Mix1 = { toString () { return ` ${ this . name }は${ this . city }で${ this . born }に生まれました` ; }, age () { const year : number = new Date (). getFullYear (); const born : number = new Date ( this . born ). getFullYear (); return year - born ; } };// Mixin 2 const mix2 : Mix2 = { toString () { return ` ${ this . name } - ${ this . city } - ${ this . born } ` ; } };// オブジェクト.assign() を使用して、ミックスインのメソッドをオブジェクトに追加しますconst mixedObj : PersonWithMixins = Object . assign ( obj1 , mix1 , mix2 ) as PersonWithMixins ;console.log ( obj1.toString ( ) ); // マルクス・アウレリウス - ローマ - 121-04-26 console.log ( `彼の年齢は今日現在${ obj1.age ( ) }です` ); //彼の年齢は今日現在 1897 歳です最初に説明したアプローチは広く普及していますが、次のアプローチはJavaScriptの言語コアが根本的に提供するもの、つまり委譲により近いものです。
2 つの関数オブジェクトベースのパターンは、サードパーティの実装を必要とせずに既に目的を達成していますextend。
interface FirstLast < T > { first () : T | undefined ; last () : T | undefined ; }// 配列型をグローバルに拡張するinterface Array < T > extends FirstLast < T > {}// 実装const EnumerableFirstLast = ( function () { const first = function < T > ( this : T []) : T | undefined { if ( this . length === 0 ) { return null ; } return this [ 0 ]; };const last = function < T > ( this : T []) : T | undefined { return this [ this . length - 1 ]; };return function ( this : any ) { this . first = first ; this . last = last ; }; }());//アプリケーション- 明示的な委譲: // [配列]の[プロトタイプ]に[first]と[last]の列挙可能な動作を適用します。EnumerableFirstLast.call ( Array.prototype ) ;// これで次のようにできます: const a : number [] & FirstLast < number > = [ 1 , 2 , 3 ]; a . first (); // 1 a . last (); // 3Pythonでは、ミックスインの概念の例がモジュール[ 20 ]socketserverに見られます。このモジュールには、クラスとクラスの両方があります。これらはそれぞれUDPおよびTCPソケットサーバーのサーバーとして機能します。さらに、ミックスインクラスとしてと の2 つがあります。通常、すべての新しい接続は同じプロセス内で処理されます。 を次のように拡張することで、次のようになります。socketserver.UDPServersocketserver.TCPServersocketserver.ForkingMixInsocketserver.ThreadingMixInsocketserver.TCPServersocketserver.ThreadingMixIn
socketserverからTCPServerとThreadingMixIn をインポートclass ThreadingTCPServer ( ThreadingMixIn , TCPServer ): passこのsocketserver.ThreadingMixInクラスは、TCPサーバーに機能を追加し、新しい接続ごとに新しいスレッドを作成します。同じメソッドを使用することで、socketserver.ThreadingUDPServerコードを重複させることなくを作成できますsocketserver.ThreadingMixIn。あるいは、を使用すると、新しい接続ごとにsocketserver.ForkingMixInプロセスがフォークされます。明らかに、新しいスレッドを作成したり、プロセスをフォークしたりする機能は、スタンドアロンのクラスとしてはあまり役に立ちません。
この使用例では、ミックスインはソケットサーバーとしての機能に影響を与えることなく、代替となる基盤機能を提供します。
Ruby の世界のほとんどは、ミックスインを中心に構築されていますModules。ミックスインの概念は、includeモジュール名をパラメータとして渡すキーワードによって Ruby で実装されています。
例:
class Student include Comparable # クラス Student は 'include' キーワードを使用して Comparable モジュールを継承しますattr_accessor :name , :scoredef initialize ( name , score ) @name = name @score = score end# Comparable モジュールを含めるには、実装クラスで <=> 比較演算子を定義する必要があります。 # 以下は比較演算子です。2 つの学生インスタンスをスコアに基づいて比較します。def <=> ( other ) @score <=> other.score end# ここからが面白いところです。Comparableインターフェースの<、<=、>、>=などのメソッドを無料で利用できます。ends1 = Student.new ( " Peter " , 100 ) s2 = Student.new ( " Jason " , 90 )s1 > s2 #真s1 <= s2 #偽Curlウェブコンテンツ言語では、インスタンスを持たないクラスでもメソッドを実装できるため、多重継承が使用されます。一般的なミックスインには、ControlUIから継承するすべてのスキンSkinnableControlUI可能な、StandardBaseDropdownUI から継承するドロップダウン メニューを必要とするユーザー インターフェイス デリゲート オブジェクト、および、などの明示的に名前が付けられたミックスイン クラスが含まれます。FontGraphicMixinバージョン7.0 ではライブラリ アクセスが追加されたため、ミックスインは同じパッケージにある必要も、パブリック アブストラクトである必要もありません。Curl コンストラクタは、インターフェースまたはミックスインを明示的に宣言することなく多重継承を使用できるようにするファクトリです。FontVisualMixinNumericAxisMixin-of
Java 8 では、インターフェースのデフォルト メソッドという新しい機能が導入されました。[ 21 ]基本的に、インターフェース クラスのプログラミング設定が完了した後に新しいメソッドをインターフェースに追加するシナリオで、インターフェースにメソッドを定義できるようになります。インターフェースに新しい関数を追加するということは、そのインターフェースを使用するすべてのクラスでメソッドを実装することを意味します。デフォルト メソッドは、いつでもインターフェースに導入でき、関連するクラスで使用される実装済みの構造を持つため、このような場合に役立ちます。したがって、デフォルト メソッドは、Java でミックスインの概念を適用する機能を追加します。
インターフェースとアスペクト指向プログラミングを組み合わせることで、C# や Java など、そのような機能をサポートする言語で本格的なミックスインを作成することもできます。さらに、マーカーインターフェースパターン、ジェネリックプログラミング、拡張メソッドを使用することで、C# 3.0 はミックスインを模倣する機能を備えています。 Dart 2.7 と C# 3.0 では、クラスだけでなくインターフェースにも適用できる拡張メソッドが導入されました。拡張メソッドは、クラスを変更することなく、既存のクラスに追加の機能を提供します。これにより、拡張メソッドを定義する特定の機能のための静的ヘルパークラスを作成できるようになります。クラスはインターフェースを実装するため (実際のインターフェースに実装するメソッドやプロパティが含まれていない場合でも)、すべての拡張メソッドも取得します。[ 3 ] [ 4 ] [ 22 ] C# 8.0 では、デフォルトのインターフェースメソッド機能が追加されました。[ 23 ] [ 24 ]
ECMAScript(ほとんどの場合JavaScriptとして実装)は、あるオブジェクトから別のオブジェクトへフィールドを段階的にコピーすることでオブジェクト合成を模倣する必要はありません。ネイティブに[ 25 ]トレイトとミックスイン[ 26 ] [ 27 ]ベースのオブジェクト合成をサポートしており、追加の動作を実装する関数オブジェクトを介して、そのような新しい機能が必要なオブジェクトにcall委譲されます。apply
Scala は豊富な型システムを備えており、トレイトはその一部であり、ミックスイン動作の実装に役立ちます。トレイトは、その名前が示すように、通常は具体的な型、あるいは少なくとも特定のインスタンスの責任とは直交する、明確な特徴や側面を表すために使用されます。[ 28 ] 例えば、歌う能力は、鳥、人などに適用できる直交する特徴としてモデル化されます。
trait Singer { def sing { println ( " 歌っています… " ) } //その他のメソッド}クラスBird はSingerを拡張しますここでは、Bird はトレイトのすべてのメソッドを自身の定義に混ぜ込んでおり、まるでクラス Bird が独自のメソッド sing() を定義したかのようです。
はextendsスーパークラスから継承するためにも使用されますが、特性の場合はextendsスーパークラスが継承されない場合に使用され、最初の特性でのミックスインにのみ使用されます。以降のすべての特性はキーワードを使用してミックスインされますwith。
クラスPersonクラスActor はPerson をSingerで拡張します。クラスActor はSinger をPerformerで拡張します。Scalaでは、クラスの新しいインスタンスを作成する際に、トレイト(匿名型)をミックスインすることができます。Personクラスのインスタンスの場合、すべてのインスタンスが歌えるわけではありませんが、歌える特定のPersonインスタンスを作成することは可能です。
class Person { def tell { println ( " 人間 " ) } //その他のメソッド}val singingPerson = new Person with Singer singingPerson.singRust はトレイトを介してミックスインを多用しています。トレイトは、Scala と同様に、定義済みの型に対して動作を実装することを可能にします。また、ジェネリクスや動的ディスパッチにも使用され、トレイトを実装する型を静的または実行時に動的に相互に交換して使用できるようにします。[ 29 ]
// 型が「話す」ことを可能にするtrait Speak { fn speak ( & self );// Rustでは、トレイトで定義された関数のデフォルト実装を実装者が定義できます。fn greet ( & self ) { println! ( "Hi!" ) } }struct Dog { name : String , }impl Speak for Dog { fn speak ( & self ) { println! ( "{} says, 'Woof woof!'" , self . name ); } }struct Robot { name : String , }impl Speak for Robot { fn speak ( & self ) { println! ( "{} says 'Beep beep boop boop', self.name" ); }// ここでは、Speak::greet の定義を Robot 用にオーバーライドしますfn greet ( & self ) { println! ( "Robot {} says 'Howdy!'" , self . name ) } }Swiftでは、プロトコル拡張のデフォルト実装と呼ばれる言語機能を使用することで、ミックスインを実現できます。
protocol ErrorDisplayable { func error ( message : String ) }extension ErrorDisplayable { func error ( message : String ) { // エラーを表示するために必要な処理を実行//... print ( message ) } }struct NetworkManager : ErrorDisplayable { func onError () { error ( "インターネット接続を確認してください。" ) } }Factor の主な言語機能: … 継承、ジェネリック関数、述語ディスパッチ、
ミックスインを備えたオブジェクトシステム