テンプレートメソッドパターン好きでした。割と。
Template Method パターン - Wikipedia
処理の流れは親クラスで定義しておいて、それぞれの処理の流れのポイントポイントでは、継承先のクラスで好きにしてねっていうスタンスですね。 テンプレートメソッドパターンと呼ぶには、ちょっと小ぶりかもしれませんが、そういう意味では1つ前の Blog 記事で書いた NULL という文字列だったら null を返して、そうじゃなかったら型変換して返すという処理もそういう風に書けますね。
こんな風に抽象クラスを定義しておいて
abstract class AbstractNullableConverter : ITypeConverter { public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) { return text == "NULL" ? null : this.Parse(text); } public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) { throw new NotImplementedException(); } protected abstract object Parse(string text); }
こういう風に登場する型に応じて継承したクラスを作ると…。
class NullableIntConverter : AbstractNullableConverter { protected override object Parse(string text) => int.Parse(text); }
まぁ、普通ですよね。でも、最近の言語はラムダ式とかサポートしてるわけなので、処理そのものを変数に格納することが出来ます。なので、処理をカスタマイズするためだけに継承するなんていう仰々しいことしなくてもいいですよね。
ということで、最近はこう。
class NullableConverter : ITypeConverter { private Func<string, object> parse; public NullableConverter(Func<string, object> parse) { this.parse = parse ?? throw new ArgumentNullException(nameof(parse)); } public object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) { return text == "NULL" ? null : this.parse(text); } public string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData) { throw new NotImplementedException(); } }
使う側で、パース処理を指定する感じ。
class RecordMap : ClassMap<Record> { public RecordMap() { this.AutoMap(); this.Map(x => x.Age).TypeConverter(new NullableConverter(x => int.Parse(x))); } }
カスタマイズしたい処理が小ぶりな場合は、こっちのほうが個人的にクラスが増えなくて好きかも。処理が大きくなりがちなときは、継承するほうを選ぶかなぁ?その場合でも、渡す処理をラムダ式じゃなくてメソッドに切り出してやればいいかな?どっちがいいんだろうか。悩ましい。
デザインパターンが考案されてまとめられたときとは、言語が持ってる機能とかも変わってきたから、選択肢も増えましたね。 関数型言語で育てられた考え方が OOP 言語で取り込まれたところが個人的に大きいと思ってます。
最近見た、こちらの記事でも継承してポリモーフィズムするケースと、コールバックを渡す方法の両方が言及されていますね。