はじめに
皆様は日頃から各プログラミング言語の特徴や機能についてキャッチアップしていますか?
今回はRubyのmixinの勉強を進めていく中でtraitとの違いが気になり、C#ではどう表現するのか気になり、勉強がてらこの記事を書くに至りました。笑
Rubyのmixinてメソッドのオーバーライドに使うってこと?Rubyってアップキャストないの??
— テク a.k.a TechNinja🥷🏼テクノロジー忍術の達人 (@TechNinja_z) February 1, 2024
うーんまだわからないなー
今更聞けない! Ruby の継承と mixin の概念を継承リストから学ぶ https://t.co/0337g9uS9L #Qiita @pink_bangbi
今回の題材について
オブジェクト指向もトレイトプログラミングもDDDも現実世界を抽象化して投影する手法であり、上手く実装すれば現実世界をモデリングすることが出来ます。
今回の題材として皆様なじみの深いUSBという現実のインターフェースをプログラミング言語のインターフェースを使ってモデリングしてみようと思います。
USB規格について
USBメモリでよく使われるUSB Type-Aは同じ口をしているのに複数のインターフェースが対応しているのをご存じですか?
使用するUSBメモリの口が同じでも対応規格がUSB3.0やUSB2.0によって転送速度に差が出ます。
※USBメモリ側がUSB3.0規格対応でもPC側がUSB2.0規格であればUSB2.0として認識され律速されます
このような現実世界を物体や動作をオブジェクト指向とトレイトプログラミングさらにパターンマッチングでうまく表現出来ないかが今回の目的です。
実装について
では早速コードですがトレイトを利用することによりDeviceが単純な継承ではなくトレイトプログラミングの特徴である平坦化(複数の継承によりふるまいを追加)によってUSB2.0規格とUSB3.0規格に対応したUSBデバイスをうまく表現することが出来ました。
※mixinは線形化(多段の継承によりふるまいを追加)によって表現されるそうです
var device = new Device();
var sampleData = byte.MaxValue;
device.Connect(Connecter.USB2);
device.WriteDataFromOS(sampleData);
// "USB2.0に接続:通常データ書き込み"
device.Connect(Connecter.USB3);
device.WriteDataFromOS(sampleData);
// "USB3.0に接続:高速データ書き込み"
device.DisConnect();
device.WriteDataFromOS(sampleData);
// "USBが接続されていません"
class Device : IUSB3, IUSB2
{
public Connecter Connecter { get; set; } = Connecter.None;
public byte Data { get; set; } = byte.MinValue;
public void Connect(Connecter connecter) => Connecter = connecter;
public void DisConnect() => Connecter = Connecter.None;
public void WriteDataFromOS(byte data)
{
var message = this switch
{
{ Connecter: Connecter.USB2 } => ((IUSB2)this).NormalSpeedWriteData(data),
{ Connecter: Connecter.USB3 } => ((IUSB3)this).HiSpeedWriteData(data),
{ Connecter: Connecter.None } => "USBが接続されていません",
_ => throw new Exception()
};
Console.WriteLine(message);
}
}
interface IUSB2 : IUSB
{
public byte NormalSpeedReadData() { return Data; }
public string NormalSpeedWriteData(byte writeData) { Data = writeData; return "USB2.0に接続:通常データ書き込み"; }
}
interface IUSB3 : IUSB
{
public byte HiSpeedReadData() { return Data; }
public string HiSpeedWriteData(byte writeData) { Data = writeData; return "USB3.0に接続:高速データ書き込み"; }
}
interface IUSB
{
public byte Data { get; set; }
}
enum Connecter
{
USB2,
USB3,
None
}
非常にスッキリと実装されたと思いませんか?
このようにトレイトが持つインターフェース毎の機能が上手く追加され、接続先がUSB2.0であればUSB2.0デバイスのようにふるまい、接続先がUSB3.0であればUSB3.0デバイスのようにふるまうことが出来ました。
もともとのDeviceの定義にはNormalSpeedWriteData(通常速度転送) もHiSpeedWriteData(高速転送) も定義されていません。これは各インターフェースのデフォルトインターフェースメソッド1を活用し各インターフェースのふるまいをDeviceに追加しています。
このように各規格に適合した処理をモデルに簡単に追加することが出来るのがトレイトプログラミングの強みだなと感じました。
ぜひ皆様もお使いのプログラミング言語で上記の例題のモデリングを試してみてください。
色々な言語の特徴を勉強させていただきたいです
コード
参考
Mixins vs. Traits
https://stackoverflow.com/questions/925609/mixins-vs-traits
Traits in Perspective
http://stephane.ducasse.free.fr/Presentations/2009-TraitsAtSC.pdf
-
C# 8のデフォルトインターフェースメソッド
https://www.infoq.com/jp/articles/default-interface-methods-cs8/ ↩