web-dev-qa-db-ja.com

コレクションにアイテムを追加する、より「自然な」方法のパターンはありますか?

コレクションに何かを追加する最も一般的な方法は、コレクションが提供するある種のAddメソッドを使用することだと思います。

class Item {}    
var items = new List<Item>();
items.Add(new Item());

そして、それについて実際に異常なことは何もありません。

しかし、なぜこのようにしないのでしょうか。

var item = new Item();
item.AddTo(items);

それは何とか自然よりも最初の方法のようです。これは、ItemクラスにParentのようなプロパティがある場合に有利です。

class Item 
{
    public object Parent { get; private set; }
} 

セッターをプライベートにできます。この場合、もちろん拡張メソッドを使用することはできません。

しかし、おそらく私は間違っており、あまり一般的ではないため、このパターンを今まで見たことがありませんか?そのようなパターンがあるかどうか知っていますか?

C#拡張メソッドはそのために役立ちます

public static T AddTo(this T item, IList<T> list)
{
    list.Add(item);
    return item;
}

他の言語はどうですか?それらのほとんどでは、ItemクラスがICollectionItemインターフェースと呼ぼうとするものを提供しなければならなかったと思います。


Update-1

私はそれについてもう少し考えていましたが、このパターンは、たとえばアイテムを複数のコレクションに追加したくない場合などに非常に役立ちます。

テストICollectableインターフェース:

interface ICollectable<T>
{
    // Gets a value indicating whether the item can be in multiple collections.
    bool CanBeInMultipleCollections { get; }

    // Gets a list of item's owners.
    List<ICollection<T>> Owners { get; }

    // Adds the item to a collection.
    ICollectable<T> AddTo(ICollection<T> collection);

    // Removes the item from a collection.
    ICollectable<T> RemoveFrom(ICollection<T> collection);

    // Checks if the item is in a collection.
    bool IsIn(ICollection<T> collection);
}

とサンプル実装:

class NodeList : List<NodeList>, ICollectable<NodeList>
{
    #region ICollectable implementation.

    List<ICollection<NodeList>> owners = new List<ICollection<NodeList>>();

    public bool CanBeInMultipleCollections
    {
        get { return false; }
    }

    public ICollectable<NodeList> AddTo(ICollection<NodeList> collection)
    {
        if (IsIn(collection))
        {
            throw new InvalidOperationException("Item already added.");
        }

        if (!CanBeInMultipleCollections)
        {
            bool isInAnotherCollection = owners.Count > 0;
            if (isInAnotherCollection)
            {
                throw new InvalidOperationException("Item is already in another collection.");
            }
        }
        collection.Add(this);
        owners.Add(collection);
        return this;
    }

    public ICollectable<NodeList> RemoveFrom(ICollection<NodeList> collection)
    {
        owners.Remove(collection);
        collection.Remove(this);
        return this;
    }

    public List<ICollection<NodeList>> Owners
    {
        get { return owners; }
    }

    public bool IsIn(ICollection<NodeList> collection)
    {
        return collection.Contains(this);
    }

    #endregion
}

使用法:

var rootNodeList1 = new NodeList();
var rootNodeList2 = new NodeList();

var subNodeList4 = new NodeList().AddTo(rootNodeList1);

// Let's move it to the other root node:
subNodeList4.RemoveFrom(rootNodeList1).AddTo(rootNodeList2);

// Let's try to add it to the first root node again... 
// and it will throw an exception because it can be in only one collection at the same time.
subNodeList4.AddTo(rootNodeList1);
13
t3chb0t

いいえ、item.AddTo(items)より自然ではありません。これを以下と混同すると思います。

_t3chb0t.Add(item).To(items)
_

items.Add(item)は自然な英語にあまり近いわけではありません。しかし、自然な英語のitem.AddTo(items)も聞こえませんね。通常、アイテムをリストに追加することになっている人がいます。スーパーで働いているときや、料理をしたり、食材を追加したりするときにそれをしてください。

プログラミング言語の場合、リストが両方を実行するように作成しました:アイテムの保存andアイテムをそれ自体に追加する責任があります。

あなたのアプローチの問題は、アイテムがリストが存在することを認識しなければならないことです。しかし、リストがまったくなくてもアイテムが存在する可能性はありますよね?これは、リストを認識してはならないことを示しています。リストはコード内では決して発生してはなりません。

ただし、リストはアイテムなしでは存在しません(少なくともそれらは役に立たないでしょう)。したがって、少なくとも一般的な形で、彼らが彼らのアイテムについて知っていればそれは問題ありません。

51
valenterry

@valenterryは、懸念の分離の問題についてかなり正しいです。クラスは、それらを含む可能性のあるさまざまなコレクションについて何も知らないはずです。新しい種類のコレクションを作成するたびにitemクラスを変更する必要はありません。

それは言った...

1。 Javaではない

ここではJavaは役に立ちませんが、一部の言語にはより柔軟で拡張可能な構文があります。

Scalaでは、items.add(item)は次のようになります。

  item :: items

::は、リストにアイテムを追加する演算子です。それはあなたが本当に望んでいるようです。それは実際には

  items.::(item)

ここで::は、Scalaリストメソッドであり、Javaのlist.add。したがって、最初のバージョンではitemitemsに自身を追加しているように見えます、実際にはitemsが追加を行っています。どちらのバージョンも有効なScala

このトリックは2つのステップで行われます。

  1. Scalaでは、演算子は実際には単なるメソッドです。 JavaリストをScalaにインポートすると、items.add(item)items add item。Scalaでは、1 + 2は実際には1。+(2)。ドットとブラケットではなくスペースを含むバージョンでは、Scalaは最初のオブジェクトを参照し、一致するメソッドを探します次のWordと(メソッドがパラメーターを取得する場合)次のものをそのメソッドに渡します。
  2. Scalaでは、コロンで終わる演算子は左ではなく右結合です。したがって、Scalaがメソッドを見つけると、右側でメソッドの所有者が検索され、左側の値がフィードされます。

多くの演算子に慣れておらず、addToが必要な場合は、Scalaに暗黙の変換という別のオプションがあります。これは2つのステップが必要

  1. たとえば、ListItemというラッパークラスを作成し、itemをそのコンストラクタであり、addToメソッドを使用して、その項目を指定されたリストに追加できます。
  2. itemをパラメーターとして取り、ListItemを返す関数を作成するitemを含む。 implicitとしてマークします。

If暗黙の変換が有効であり、Scalaが

  item addTo items

または

  item.addTo(items)

アイテムにaddToがない場合、現在のスコープで暗黙的な変換関数を検索します。 doesaddToメソッドを持っている型を変換関数のいずれかが返す場合、これは起こります:

  ListItem.(item).addTo(items)

これで、暗黙の変換が好みに近づく可能性がありますが、特定のコンテキストですべての暗黙の変換を明確に認識していない場合、コードが予期しない動作を行う可能性があるため、危険が追加されます。これが、Scalaでこの機能がデフォルトで有効にならない理由です。 (ただし、自分のコードでそれを有効にするかどうかを選択できますが、他の人のライブラリではそのステータスについて何もできません。ここではドラゴンになります)。

コロンで終了する演算子は醜いですが、脅威にはなりません。

どちらのアプローチでも、懸念の分離の原則が維持されます。 lookは、itemがコレクションについて知っているかのように作成できますが、実際には作業は別の場所で行われます。この機能を提供したい他の言語は、少なくとも同じように注意する必要があります。

2。 Java

構文を拡張できないJavaでは、最善の方法は、リストを認識するラッパー/デコレータクラスを作成することです。アイテムがリストに配置されるドメインでは、それらをその中にラップできます。彼らがそのドメインを離れたら、彼らを連れ出してください。多分 visitor パターンが最も近いでしょう。

3。 tl; dr

Scalaは、他のいくつかの言語と同様に、より自然な構文で支援できます。 Javaは、醜いバロック様式のパターンしか提供できません。

4
itsbruce

拡張メソッドを使用しても、Add()を呼び出す必要があるため、コードに役立つものは何も追加されません。 addtoメソッドが他の処理を行わない限り、それが存在する理由はありません。また、機能的な目的を持たないコードを記述することは、読みやすさと保守性に悪影響を及ぼします。

一般的でないままにしておくと、問題はさらに明白になります。これで、さまざまな種類のコレクションについて知る必要があるアイテムができました。これは、単一責任の原則に違反しています。アイテムはデータのホルダーであるだけでなく、コレクションも操作します。これが、コレクションにアイテムを追加する責任を、それらの両方を消費するコードにまで任せている理由です。

2
control

「追加」という言葉に執着していると思います。代わりに、「collect」などの代替のWordを探してみてください。これにより、パターンが変更されず、より英語に適した構文が得られます。

_items.collect(item);
_

上記のメソッドはitems.add(item);とまったく同じですが、唯一の違いはWordの選択が異なり、英語で非常にうまく「自然に」流れます。

場合によっては、単に変数、メソッド、クラスなどの名前を変更するだけで、パターンの再設計に負担がかかるのを防ぐことができます。

1
RestInPeace

Javaでは、典型的なコレクションはthings-を保持しませんreferencesを保持します。より正確にはcopies of referencesを保持します。物事に。対照的に、ドットメンバー構文は、一般に、参照自体ではなく、参照で識別されるものに作用するために使用されます。

ボウルにAppleを置くとAppleの一部の側面(たとえば、場所)が変更されますが、「オブジェクト#29521」と書かれた紙片をボウルに入れても、オブジェクト#29521であっても、Appleを変更してください。さらに、コレクションは参照のコピーを保持するため、「オブジェクト#29521」と言った紙切れをボウルに入れるのと同等のJavaはありません。代わりに、番号#29521を新しい紙片にコピーし、それをボウルに見せ(与えないで)ください。これにより、コピーが作成され、オリジナルは影響を受けません。

実際、Javaのオブジェクト参照(「紙片」)は受動的に監視できるので、参照もそれによって識別されるオブジェクトも、それらで何が行われているのかを知る方法がありません。コレクションの存在をまったく知らない可能性のあるオブジェクトへの一連の参照を保持するのではなく、その中にカプセル化されたオブジェクトと双方向の関係を確立するコレクションの種類があります。一部のそのようなコレクションでは、それ自体をコレクションに追加するようにメソッドに要求することが理にかなっている場合があります(特に、オブジェクトが一度に1つのコレクションにしか存在できない場合)。ただし、一般に、ほとんどのコレクションはそのパターンに適合せず、オブジェクトへの参照を受け入れることができますが、それらの参照によって識別されるオブジェクトの部分には一切関与しません。

1
supercat

一般的なケースにはそのようなパターンはありませんが(他の人が説明しているように)、同様のパターンにメリットがあるコンテキストは、ORMにおける双方向の1対多の関連付けです。

このコンテキストでは、ParentChildの2つのエンティティがあるとします。親は多くの子を持つことができますが、各子は1つの親に属します。アプリケーションが関連付けを両端からナビゲート可能(双方向)にする必要がある場合は、親クラスに_List<Child> children_を、子クラスに_Parent parent_を指定します。

もちろん、協会は常に相互的であるべきです。 ORMソリューションは通常、ロードするエンティティにこれを適用します。ただし、アプリケーションがエンティティ(永続または新規)を操作しているときは、同じルールを適用する必要があります。私の経験では、ほとんどの場合、Parent.addChild(child)を呼び出すChild.setParent(parent)メソッドを見つけますが、これは公開されていません。後者では、通常、例に実装したのと同じチェックが見つかります。ただし、もう1つのルートを実装することもできます:Child.addTo(parent)を呼び出すParent.addChild(child)。どのルートが最適かは状況によって異なります。

1
Maarten Winkels