データ構造における境界
これは、module的に見れば、bもcも全てがpublicなものとして定義されているからそうなっていると見れる
classを使った場合は、propertyをprivateにし、getterを用意する
でもこれだけでは意味がない
a.getA().getB().getC()とアクセス可能である
いくつかアプローチが考えられる
Interfaceを使う
なんか別の方法でmoduleの抽象度を規定する
aという抽象度からは、bのみが見えるべきで、cという具体は見える必要がない
bが境界になっている
cの構造が変わった時に変更が必要なのはbのみで、aには影響しない
getterを用意する
getValueを公開する
valueを使いたい場合は、a.b.c.valueではなくgetValue()する
a.b.c.valueという構造が変わっても影響を受けない
しかし、a.b.c.valueと書くことを防げてはいない
目視チェックするしかない
中身を知らないことと、アクセスの有無って別の話では?
type B = C1 | C2 | C3のように表現した時、どういう意味が生じるのか?
b :: Bという値があった時に、
これを扱うものは、個別のCの構造は知らずに
その寄せ集め、抽象化されたBを扱いたい
ということになる
実際は、パターンマッチ等すれば、個別のCにアクセスすることはできる
抽象化はしてるけど、Cの中身を知らないわけではない
「パターンマッチ等しないと個別のCにアクセスできない」のはそれでおkなのでは
パターンマッチが境界になってる
単純なa.b.cのようなアクセスはできない
実際は共通propertyのみそういうアクセスができるが、それはpublicになっているとみなせる?
パターンマッチの処理自体が、内側のモジュール内にのみ存在するようにする
haskellだと、AやBをprivateにして、getValueのような関数のみを公開することで、valueを得るためには、getterを使わせる、というように強制できる
code:hs
module DataStructure where
data A = A { value :: Int } deriving (Show)
data B = B { a :: A } deriving (Show)
data C = C { b :: B } deriving (Show)
code:hs
module Getter (getValue, C(..)) where -- これだけ公開
import DataStructure (C(..), B(..), A(..))
getValue :: C -> Int
getValue c = value (a (b c))
全てのpropertyアクセスがそもそも関数経由なので、関数自体をprivateにすれば、データ構造のpropertyもprivateになる
Haskellにおいては、MaybeやEitherなどの抽象があり、さらにその抽象であるFunctorやMonadがある。多くの関数や言語機能はこれらの抽象に依存させることで堅牢になっている
そもそもポリモーフィズムというのが具体ではなく、ちゆうしように依存するように設計せれた機能
これ↓に関してはほぼ全く意味がない
例えば、type Size= {width: Width, height: Height}のような型を導入して、
Widthをpackage privateにして、
package外からはWidthを直接importするのではなく、Size['Width']のようにしてアクセスする
これにどれほどの意味があるのか
Sizeに依存するものの数が増える
Widthの被依存数は減る
Widthに直接アクセスしているものは全てpackage内のものであることがわかる
だが実際は間接的にWidhtにアクセスされている
逆に、Sizeだけを公開していると、
heightとwidthの両方に対する依存をSizeを経由することになる
Sizeへの依存を緩和するために、heightとwidthに直接依存する
ということにどういう意味があるのか、何が改善されているのか
境界を作ることによって意味があるかどうかを考えないといけない
仮にgetWidthというのがあって、その内部でゴニョゴニョ計算しているのであれば、
getWidthというインターフェースによって、ゴニョゴニョ計算が隠される
これは良いとだ
一方で、上記のsizeの話だと、size.widthと直接widthには全く何も益がないのでは
経由して直接依存してるので
そのデータ構造を永続化しないのであれば、primitiveな構造を作っておいて、常に関数経由でアクセスするようにするとか
code:ts
type D = { すごいprimitiveな構造 } // private?
export const mkD = (...): D => {..}
export const getLength = (d: D): number => {..}
D内のデータ構造は公開されてしまうが、利用者が意味不明なぐらいprimitiveだったら、まあ関数使うか、となる(?)
外向きの構造と内向きの構造を用意する
code:ts
export type D = {外向き. だいぶあばうと}
type D_ = {内向き. そこそこ詳細}
export const mkD = (..): D_ => {..}
ん、こんなことできるのだろうか
classを使う
code:ts
export class AA {
constructor(private size: { height: number; width: number }) {
this.size = size;
}
getHeight() {
return this.size.height;
}
getDoubleHeight() {
return f(this.size.height);
}
}
const f = (height: number) => {
return height * 2;
};
データ構造内のアクセス制御のためだけに使う
上記のように書けばクラスAAが内部でsizeという構造を持っていることは利用者に伝えずに、内部用の構造として使える
publich methodで制御すればデータ構造のネストの中にもアクセスできなくできる
計算ロジックはprivate methodとして持たせてもよいがクラスの制限を食らうのでmodule内に普通のprivate functionとして書いて使う
getterはデータ構造を返すのではなく、必要な値だけを返す
これでデータ構造に対するアクセス制御はできる
しかし、classというものの力が過剰でノイズが増える
instantiateしないといけなかったり、「このclassは状態を持つのか?」とか憂慮しないといけなかったりしてだるい