2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

コンパイル時に関連プロパティを自動で追加する属性

Last updated at Posted at 2013-05-21

モチベーション

次のような何かデータベース上のテーブル行を表す、ありがちそうなクラスを考える。

# python じゃないよー。Booだよー。
class Person:
	public Id as int
	public Name as string
	public Age as int
	# その他いろんなもの

	# 指定idのデータを取得する static メソッド。
	public static def Get( id as int ) as Person:
		# ...

きっとこのテーブルは、次のような同様のクラスからIdを使って参照されている。データベースに乗っていたり、シリアライズされたりするだろうから、Person を直接参照することはできない。

class Family:
	public FatherId as int  # PersonのId
	public MotherId as int   # PersonのId
	# その他いろんなもの

このFamilyクラスインスタンスから、各メンバーのPersonを取得するのは簡単だけど面倒くさい。

f as Family
print Person.Get(f.FatherId).Name
print Person.Get(f.MotherId).Age  # 女性の年齢を尋ねるとは不謹慎な!

Personから更に別のIdを使った参照があるとダラダラと Get() なメソッドが連鎖してしまう。

目標

上のFamilyが次のようなコードとなるような自動化の仕組み。

class Family:
	public FatherId as int
	public MotherId as int

	public Father as Person:  # このプロパティをコンパイル時に自動挿入
		get:
			return Person.Get(FatherId)

	public Mother as Person:  # このプロパティをコンパイル時に自動挿入
		get:
			return Person.Get(MotherId)

解決方法:GetするPropertyを勝手に追加してくれる属性を作る。

コンパイル時にGet()してくれるプロパティ構文木を追加する次の属性を作る。

import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast

# [IdField(クラス名)] とすると "return クラス名.Get()" というプロパティを追加生成する属性。
# AbstractAstAttribute は構文木を書き換える属性のためのクラス。Apply()をオーバーライドして属性適用時の処理を書く。
public class IdFieldAttribute( AbstractAstAttribute ):

	private refclass as ReferenceExpression

	def constructor( cls as ReferenceExpression ):
		refclass = cls

	def Apply( node as Boo.Lang.Compiler.Ast.Node ):
		# c = 属性のついた構文木ノードの直近のclass定義
		c = node.GetAncestor[of ClassDefinition]()
		assert c != null
		# idf: 属性のついたノードがフィールドかどうか。Idで終わっているかどうか等チェック。
		idf = node as Field  # フィールドの構文木ノードクラスは Boo.Lagn.Compiler.Ast.Field
		return if idf == null or not idf.Name.EndsWith( 'Id' ) or idf.Name == 'Id' or not idf.IsPublic or idf.IsStatic

		# フィールド名から Id を除いた名前でプロパティ構文木生成。
		n = idf.Name[:-2]
		pn = ReferenceExpression( n )
		prop = [|
			public $(pn) as $(refclass):
				get:
					return $(refclass).Get($(ReferenceExpression(idf.Name)))
		|]
		prop.LexicalInfo = idf.LexicalInfo

		# 作ったプロパティをクラスにメンバとして追加する。
		c.Members.Add( prop )

このソースを IdFieldAttribute.dll としてビルドする。次のような感じ。

$ booc -o:IdFieldAttribute.dll IdFieldAttribute.boo

このdllを他のプログラムコンパイル時に参照することで、次のようにIdField 属性を使用出来る。Person については前述のものと同じ。

class Family:
	[IdField(Person)]   # Father as Person というプロパティができる
	public FatherId as int
	[IdField(Person)]  # Mother as Person というプロパティができる
	public MotherId as int

f = Family( FamilyName:'Taira', FatherId:1, MotherId:2 )
print f.Father.Name   # f.Father で直接 Person が取得できる。
print f.Mother.Name   # 女性の年齢を聞くのはやめよう。

注意点

名前違いのプロパティを構文木レベルで追加しているだけなので、同名のメンバーがすでに定義されていたら、当然コンパイルエラー。

IQuackFu

実は IQuackFu インターフェースを使うと、上記の方法よりももっと簡単でメンテナンス性のいい実装になったりはする。

ただ、IQuackFu は型指定できない(objectだけ)ので、型違いのエラーを実行時にしか検出できない。

結論

まぁまぁ便利だけど、この IdField はどうしてもほしい物か?と聞かれるとやや疑問ではあるかも。

好き勝手な属性を作り続けると、何をやってるのかわからない大変なプログラムになるかもしれない。

ただ、このぐらいなら許されるような気がする。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?