web-dev-qa-db-ja.com

どのクラスが別のクラスを更新できるかを制限するパターンはありますか?

たとえば、書き込み可能なパブリックプロパティDataを持つクラスImportantInfoがあるとします。多くのクラスがこのプロパティを読み取りますが、設定するクラスはごくわずかです。基本的に、データを更新したい場合は、自分が何をしているかを本当に理解している必要があります。

文書化する以外に、これを明示的にするために使用できるパターンはありますか?たとえば、IUpdateImportantDataを実装するクラスのみがそれを実行できるようにするいくつかの方法(これは単なる例です)?ここではセキュリティについて話しているわけではありませんが、「ちょっと、本当に実行しますか?」ものの種類。

更新

私は以前に同様の状況に直面したことがあります。そのため、質問をより一般的なものにしようと試みましたが、より具体的な例が間違いなく必要であるようです。だからここに行く:

CurrentYearプロパティを持つContextクラスがあります。多くのクラスはこのContextオブジェクトを使用し、CurrentYearが変更されるたびに、対応する必要があります(たとえば、自分自身を再読み込みする)。現在、CurrentYearを正当に変更できるクラスがいくつかあります。もちろん、他の多くのクラスに影響を与えるため、CurrentYearを全員に変更してほしくない。それについてどう思いますか?

7
Mike

だから、あなたは現在持っています:

public class ImportantInfo {
    // constructor
    public ImportantInfo() { ... }

    // getter
    public Data getData() { ... }

    // dangerous setter
    public void setData(Data d) { ... }
}

可変性の管理

まず、デフォルトでsetメソッドを含めたくない場合があります。もう1つは、おそらくパブリックコンストラクターを必要としないことです。多分あなたはすでにファクトリーを持っていますが、あなたのファクトリーは不変のインターフェースを返す可能性があります(それはsetメソッドを公開しません)。次のインターフェースには、変更可能な実装を返すメソッドがありますが、これには作業が必要であり(人々が誤ってそれを行わないため)、メソッドの名前が警告を提供します。

public interface ImportantInfo {
    public Data getData();

    // ... expose any other safe methods here ...

    public ImportantInfoImplementation getMutableImplementionButBeCareful();
}

ImportantInfoのプライベートコンストラクターと、不変のインターフェイスを返すファクトリメソッドを使用します。

public class ImportantInfoImplementation {
    // constructor is now private so people can't instantiate this
    // willy-nilly.
    private ImportantInfoImplementation() { ... }

    // public factory method is now the way to get instances of this class
    // and you only get the interface which doesn't expose the dangerous
    // method.
    public static ImportantInfo of() {
        return new ImportantInfoImplementation();
    }

    // getter is mentioned in the interface
    @Override
    public Data getData() { ... }

    // dangerous setter is hidden by the interface.
    public void setData(Data d) { ... }

    // returns the mutable implementing class.
    @Override
    public ImportantInfoImplementation getMutableImplementionButBeCareful() {
        return this;
    }
}

危険なメソッドを使用するには、クライアントは次を呼び出す必要があります。

ImportantInfo ii = ImportantInfoImplementation.of();

// Living free and easy with safe immutable version.
someUntrustedMethod(ii);

// probably not messed up yet!
ii.getData();

// OK, I want it badly enough...
ii.getMutableImplementionButBeCareful().setData(d);

@Izkataは上記のようなものを狙っていたと思いますが、よくわかりません。

不変性の活用

Setメソッドが潜在的な危険を露呈しているかどうか、またはこのクラスのインスタンスが不安定になるかどうかはわかりません。前者の場合、これはあなたを助けませんが、後者の場合、これはあなたの問題を解決するかもしれません:クラスを不変にします:

public class ImportantInfo {
    private static final Data data;

    // constructor
    public ImportantInfo(Data d) {
        data = d;
    }

    // getter
    public Data getData() { return d; }

    // no-longer dangers setter returns a new ImportantInfo leaving
    // the old one unchanged.
    public ImportantInfo setData(Data d) {
        return new ImportantInfo(d);
    }
}

SetData()を呼び出すと、古いImportantInfoオブジェクトは変更されず、クライアントコードは、変更された状態を反映する新しいImportantInfoオブジェクトを取得します。もちろん、これが適切に機能するには、Dataオブジェクトも不変である必要があります。データが何らかのコレクションであるか、コレクションを保持している場合、私の Javaでコレクションを不変にするためのヒント が役立つかもしれません。 2017-09-14を更新:その代わりに、Javaのほとんどの変異を排除または管理するために Paguro を使用しています。

非推奨

それでも満足できない場合は、setData()メソッドを廃止して、警告を使用しないようにする必要があります。私もこれがあまり好きではありませんが、それは、このメソッドを呼び出すのが悪い理由を想定していないためかもしれません。

// Be careful when setting data because
//  1. first reason
//  2. second complication
//  3. ...
@Deprecated
public void setData(Data d) { ... }

結論

いずれにせよ、私は人々に安全なものに簡単にアクセスできるようにするというアイデアが好きですが、安全でないものを取得するためにより多くの仕事をさせるようにします。ただし、これを行う場合は、注意が必要なwhatを正確に説明してください。また、なぜそれが問題であり、人々が使用する前に何をしているのかを人々に知らせるためです。

7
GlenPeterson

神聖なすべての愛のために、これをしようとしないでください。クラスの設計とカプセル化のポイントを完全に逃しています。

OOPでは、クラスには invariants があります。これらは、クラスが正しく機能するために必要な条件です。ダムの「プロパティバッグ」(AKA Data Transfer Object )でない限り、ほとんどすべてのクラスに少なくともいくつかの不変式があり、これらの多くはアンチパターンと見なされます( Anemic Domainモデル )。

しかし、要点に戻ります。したがって、クラスの責任は次のとおりです。

  • パラメータ化されたコンストラクタと検証により、その初期状態が有効であることを確認します。
  • 外部からの信頼はありません。すべてのpublicおよびおそらくすべてのprotectedメソッドとプロパティセッターへの引数を検証します。
  • 無効な状態になる操作の実行を拒否します(たとえば、例外をスローするか、状態の他の部分を調整して有効なままにするか、単に何もしないでエラーステータスを返すなど)。または、さらに良いのは、型システムを使用して、コンパイル時のエラーが無効な値を提供するようにすることです。文字列/整数パラメーターの代わりに列挙を使用する。

これは カプセル化 です。オブジェクトは、絶対に必要ではないものへの外部アクセスを許可しません。また、オブジェクトがアクセスを提供する場合は、非常に慎重に許可します。

したがって、これを念頭に置くと、適切に設計されたクラスが誰を呼び出すかを気にする必要がないことは明らかです危険なことを行うことは不可能であるため-または、少なくとも何かmoreクラスが実際に行うように設計されているものよりも危険です。クラスがintendedで危険な機能を提供する場合は、おそらく別のレベルの抽象化が必要です。つまり、信頼できる呼び出し元のみが危険なオブジェクトへの参照を取得できるようにアプリケーションを設計する必要があります。そもそも

後者の一般的な例は、データアクセスです。通常、実際の接続オブジェクトをユーザーインターフェイスコンポーネントに提供しないでください。ここでは、データの破損や、さらに悪い場合には、SQLインジェクションやその他のセキュリティの脆弱性のリスクが発生する可能性があります。代わりに、encapsulate接続であるリポジトリまたはコマンド/クエリオブジェクトを提供します。

さえif目を見張るようなコードを思いついたreliably呼び出し元を検証する-リリースモードで実行しているとき、またはバイナリリライタまたはインターセプトプロキシが関与しているときでも-可能性があるruntimeでのみ実行してください。つまり、プログラマーはno clue実際に試すまで何か問題があることを意味しますrunこれは、維持しようとしている貧しい野郎を治療するための恐ろしい方法ですそれは後で。また、このプレースホルダーインターフェースや属性をいたるところに持ち運ぶ必要があるため、依存関係がめちゃくちゃになってしまいます。

次に、「推移的な信頼」の問題があります。つまり、callingクラスは、決定したインターフェイスを実装する可能性がありますが、thatクラスを呼び出しているクラスを信頼しますか?おそらく、メンテナンスプログラマーはこの制限に非常にイライラするため、技術的に要件を満たすラッパークラスを作成するだけで、厄介なランタイムエラーを心配することなく使用できます。

繰り返しますが、 /呼び出し元の検証を試みないでください。必要に応じてargumentsを検証し、呼び出し元を検証する必要がないようにクラスを設計します。

興味深い事実:.NET Frameworkの元のバージョンは実際にはhad本質的に、呼び出し元と呼び出しスタック全体を検証するためのシステムです。それは Code Access Security と呼ばれていました。ポリシーシステムは非常に複雑で、扱いにくく、一般に.NETコミュニティによって軽視されていることが判明しました Microsoftは実際に.NET 4で削除することを決定しました 。 CAS自体の痕跡はまだ残っていますが、すべてはアセンブリとAppDomains(サンドボックス)に関するインフラストラクチャレベルのものです。 sedが実際に発信者検証の防弾手段であるという事実フレームワークから削除されましたは、プログラミングコミュニティ全体がこの慣行について何を考えているかについて多くのことを教えてくれるはずです。

10
Aaronaught

プライベートメンバーに切り替えてゲッター/セッターを公開すると、一種のことが頭に浮かびます。NETを実行したことがなく、Javaを使用していないので、これは私が考えている疑似コードです。 :

class SomeFickleClass {
   public interface IUpdateImportantData {
      public Data getData();
   }

   private Data myData;

   public function setData(IUpdateImportantData Thing) {
      myData = Thing.getData();
   }
   public function getData() {
      return myData;
   }
}

そして、使用法は次のようなものになります。

final Data newData = ....
fickleClassInstance.setData(new SomeFickleClass.IUpdateImportantData() {
   public Data getData() {
      return newData;
   }
});

したがって、これはIUpdateImportantDataを必要とするインターフェイスに更新を強制し、匿名クラスインスタンスを使用してその場で実行できます。また、これは少し厄介で、頻繁に使用することはできません。

2
Izkata

@GlenPetersonの回答に基づいて、これから私がやろうとしていることを説明します。 2つのクラスがあります。

  • 環境。このクラスは変更可能で、SetDataメソッドを提供します。
  • ContextObserver。このクラスにはContextへのプライベート参照があり、Contextでの変更をリッスンし、Contextが変更されると、実行するように指示したメソッドを実行します。また、不変バージョンのContextを返すメソッドGetContextもあります。

したがって、実際にContextのデータを変更する必要がある人はContextオブジェクトへの参照を取得し、データを消費するだけの人はContextObserverクラスへの参照を取得します。

単純明快で、特定のコンシューマーに対してクラスを読み取り専用にする場合は、元のクラスの読み取り専用ラッパーを提供します。

1
Mike

質問で述べたように、重要なデータコンテナーがデータの設定内容を「知る」ための方法を作成しようとしていました-出方

それから私はこれがあなたが望むものだと思います:

public interface IUpdateImportantData {
    public Data newDataValue();
}

public class ImportantInfo {
    // constructor
    public ImportantInfo() { ... }

    // getter
    public Data getData() { ... }

    // safer setter
    public void setData(IUpdateImportantData iuid) {
        // instanceof always returns true when used on null
        if (iuid == null) {
            throw new IllegalArgumentException("Can't set data with null");
        }
        if ( (iuid instanceOf OkClass1) ||
             (iuid instanceOf OkClass2) ) {
            this.data = iuid.newDataValue();
        } else {
            throw new IllegalArgumentException("That class is not allowed to change data");
        }
    }
}

次に使用例を示します。

public OkClass2 implements IUpdateImportantData {
    @Override
    public Data newDataValue() { return Data(3); }
}

public OtherClass implements IUpdateImportantData {
    @Override
    public Data newDataValue() { return Data(3); }
}

ImporatntInfo ii = new ImportantInfo();
OkClass2 oc2 = new OkClass2();
ii.setData(oc2); // works.

OtherClass oc1 = new OtherClass();
ii.setData(oc1); // throws exception.

私は今あなたの質問に答えたと思いますが、これはより複雑な問題を包んでいます。根本的な問題を修正することは常に優れています。

制御できないコードやサービスとのインターフェースが必要になる場合があります。根本的な問題を修正できない場合は、そのAPIを誰が使用しているかを気にすることなく、根本的な問題の有効な使用のみを許可するAPIを設計できます。無効なusESの防止は、無効なusERSの防止よりもはるかに優れています。あなたはできる:

  • ImportantInfoにアクセスする必要がある1つのクラスのプライベート内部クラスを作成しますか?

  • ImportantInfoにアクセスする必要があるいくつかのクラスと同じパッケージ(フォルダー)にImportantInfoを置き、setData()にデフォルトのアクセス権を与えます(package-scope、no-modifierを指定)?

  • ImportantInfoを2つのクラスに分割します。1つは安全なものを保持し、もう1つは危険なものを保護するだけですか。

  • 重要なデータの変更に伴う危険を防ぐために、重要な情報を再設計しますか?

  • 基になるデータを変更不可にします。元のデータを変更せずに、必要に応じて新しいバージョンのデータを挿入します。

本当に、データの変更に関して何が危険かを正確に理解していれば、より良い提案をすることができます。あなたがそれを理解しているなら、最善の解決策はおそらく私の助けなしに現れるでしょう。そうでない場合は、問題の根本的な原因を突き止めて解決するために、いくつかの実験を行って、その周りに複雑なレイヤーを構築するのではなく、.

0
GlenPeterson

データを更新するためにImportantInfoのインスタンス内をポークする方法を知っている他のクラスがいくつかある場合は、それが安全であるかそうでないかではなく、次の違反が増えるため、気になるアプローチのようです。デメテルの法則。

代わりに、更新コードをクラスの内部に置き、他のクラスに実行する更新の指示を渡すだけの方が適切です。単純なケースでは、これらの命令はメソッドへの単なる引数ですが、より複雑なことを実行したい場合は、命令を独自の小さなクラスにパッケージ化できます(これは、おそらく機能の自由にできるだけ近いはずです) :これは単なる説明であり、Java用語)のPOJOです。更新コード自体は、クラス内にあるため、クラスの他の部分と同期して、はるかに簡単に管理できます。


また、状態の変更を本当にサポートする必要があるかどうかも真剣に検討します。多くの場合、オブジェクトを完全に不変にして、代わりに新しいオブジェクトを返すことで変更を処理できます。これはより機能的なプログラミングアプローチであり、オブジェクトの更新を行わないユーザーは、変更が背後から発生しないと想定できることを意味するので、かなりうまく機能します。これでプログラミングがどれほど簡単になるか、言い過ぎることはありません。

外部からの更新をサポートするhaveの場合は、代わりにオブジェクトの真の読み取り専用デリゲートを指定して、コンシューマが誤って内部を突入できないようにすることを検討してください。 Javaでは、 プロキシメカニズム を使用してこれを行うことを検討し、リーダーメソッドの1つがIDを返すようにします(ライターが他のマネージャークラスに提示する必要があるIDを取得します)更新可能なエンティティ)。一部のフレームワークでは、これを非常に簡単に行うことができます…

0
Donal Fellows