循環参照によるF#タイプを注文する方法を決定する問題
-
27-09-2019 - |
質問
共通のタイプを拡張するいくつかのタイプがあり、これらは私のモデルです。
次に、CRUD操作のモデルタイプごとにDAOタイプがあります。
モデルタイプの特定のIDを見つけることができる関数が必要になるので、いくつかのその他の関数の新しいタイプを作成しました。
問題は、これらのタイプを注文する方法がわからないということです。現在、私はDAOの前にモデルを持っていますが、どういうわけか必要です DAOMisc
前 CityDAO
と CityDAO
前 DAOMisc
, 、それは不可能です。
簡単なアプローチは、この関数を各DAOに配置し、その前に来ることができるタイプだけを参照することです。 State
前に来る City
なので State
との外国の重要な関係があります City
, 、したがって、その他の関数は非常に短いでしょう。しかし、これは私を間違っているように私を襲うので、これに最適なアプローチ方法がわかりません。
これが私の雑多なタイプです BaseType
私のすべてのモデルの一般的なタイプです。
type DAOMisc =
member internal self.FindIdByType item =
match(item:BaseType) with
| :? StateType as i ->
let a = (StateDAO()).Retrieve i
a.Head.Id
| :? CityType as i ->
let a = (CityDAO()).Retrieve i
a.Head.Id
| _ -> -1
これが1つのDAOタイプです。 CommonDAO
実際にはCRUD操作のコードがありますが、ここでは重要ではありません。
type CityDAO() =
inherit CommonDAO<CityType>("city", ["name"; "state_id"],
(fun(reader) ->
[
while reader.Read() do
let s = new CityType()
s.Id <- reader.GetInt32 0
s.Name <- reader.GetString 1
s.StateName <- reader.GetString 3
]), list.Empty
)
これが私のモデルタイプです:
type CityType() =
inherit BaseType()
let mutable name = ""
let mutable stateName = ""
member this.Name with get() = name and set restnameval=name <- restnameval
member this.StateName with get() = stateName and set stateidval=stateName <- stateidval
override this.ToSqlValuesList = [this.Name;]
override this.ToFKValuesList = [StateType(Name=this.StateName);]
これの目的 FindIdByType
関数は、外部キー関係のIDを見つけたいので、モデルに値を設定し、CRUD関数にすべての正しい情報を使用して操作を実行できることです。それで、 City
状態名のIDが必要なので、私は状態名を取得し、それをに入れます state
入力して、この関数を呼び出してその状態のIDを取得するため、私の市の挿入には外部キーのIDも含まれます。
これは、インサートを処理するための非常に一般的な方法で、私が解決しようとしている現在の問題です。
アップデート:
他のすべてのDAOが定義された後、まるで閉鎖であるかのように定義された後、何らかの形でfindidbytypeメソッドをCommondaoに注入できるかどうかを調査して確認する必要があります。これがJavaの場合、AOPを使用して、F#でこれを行う方法は確かではありません。
最終更新:
私のアプローチについて考えた後、私はそれが致命的に欠陥があることに気づいたので、私は別のアプローチを思いつきました。
これが私がインサートを行う方法であり、私はこのアイデアを各エンティティクラスに入れることにしました。これはおそらくより良いアイデアです。
member self.Insert(user:CityType) =
let fk1 = [(StateDAO().Retrieve ((user.ToFKValuesList.Head :?> StateType), list.Empty)).Head.Id]
self.Insert (user, fk1)
私は使用し始めていません fklist
それでも、それはそうです int list
そして、私はどの列名がそれぞれに行くか知っているので、私はただする必要があります inner join
たとえば、選択の場合。
これは、一般化されたベースタイプの挿入物です。
member self.Insert(user:'a, fklist) =
self.ExecNonQuery (self.BuildUserInsertQuery user)
F#がCo/Contra-Varianceを実行できればいいでしょう。そのため、その制限を回避する必要がありました。
解決
この例は、機能的なプログラミングに慣れているものとはほど遠いものです。しかし、相互に再帰的なタイプを注文する問題には、標準的なソリューションがあります。タイプパラメーターを使用して2レベルのタイプを作成します。関連言語であるOCAMLで簡単な例を挙げます。単純な例を使用している恐ろしいタイプ関数に翻訳する方法がわかりません。
これがうまくいかないことです:
type misc = State of string
| City of city
type city = { zipcode : int; location : misc }
2レベルのタイプで修正する方法は次のとおりです。
type 'm city' = { zipcode : int; location : 'm }
type misc = State of string
| City of misc city'
type city = misc city'
この例はOCAMLですが、F#に一般化することができます。お役に立てれば。
他のヒント
F#では、定義することができます 相互に再帰的なタイプ, 、つまり、お互いを一緒に参照する必要がある2つのタイプを定義することができ、それらはお互いを見ることができます。これを書くための構文は次のとおりです。
type CityDAO() =
inherit CommonDAO<CityType>(...)
// we can use DAOMisc here
and DAOMisc =
member internal self.FindIdByType item =
// we can use CityDAO here
この構文の制限は、両方のタイプを単一のファイルで宣言する必要があるため、1ファイルごとに典型的なC#組織1タイプを使用できないことです。
ノーマンが指摘しているように、これは典型的な機能設計ではないため、データアクセスレイヤー全体をより機能的な方法で設計した場合、おそらくこの問題を回避できます。ただし、F#で機能的なスタイルとオブジェクト指向のスタイルを組み合わせることには何の問題もないので、相互に再帰的なタイプを使用することが唯一の選択肢かもしれません。
おそらく、2つのタイプのインターフェイスを最初に定義する場合、おそらくコードをより適切に記述できます - これらは相互に再帰的である必要がある場合とそうでない場合があります(一方が他のパブリックインターフェイスで使用されているかどうかによって異なります):
type ICityDAO =
abstract Foo : // ...
type IDAOMisc =
abstract Foo : // ...
これには次の利点があります。
- 単一のファイルで相互に再帰的なすべてのインターフェイスを定義すると、コードの読み取りが低くなりません
- 後でインターフェイスを参照することができますので、他のタイプは相互に再帰的である必要はありません
- 副作用として、より拡張可能なコードがあります(インターフェイスのおかげ)
F#は相互に再帰的なタイプを直接サポートします。次の鶏/卵の種類の定義を考えてみましょう。
type Chicken =
| Eggs of Egg list
and Egg =
| Chickens of Chicken list
ポイントは、相互に再帰的なタイプが 'と'オペレーターを使用して一緒に宣言されることです(2つの別々のタイプとは対照的に)
daomisc.findidbytypeを排除し、各DAOクラス内のFindIDに置き換えるのはどうですか? FindIDは、独自のタイプを見つける方法のみを知っています。これにより、基本クラスと動的タイプテストの必要性、およびDAOMISCと他のすべてのDAOクラスの間の円形の依存性が排除されます。 DAOの種類は他の1人に依存する可能性があるため、CitydaoはStatedao.findidに電話することができます。 (DAOタイプは、必要に応じて相互に依存する可能性があります。)
これはあなたが言っていたことですか?関数はその前に来るタイプのみを参照すると言ったので、私はよくわかりません。私がここで提示しているという考えは、各findID関数が独自のタイプのみを知っているということです。