컬렉션에 아이템을 추가하는보다 "자연적인"방법을위한 패턴이 있습니까? [닫은]


13

컬렉션에 무언가를 추가하는 가장 일반적인 방법은 컬렉션이 Add제공하는 일종의 방법 을 사용하는 것입니다 .

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

실제로는 그다지 특이한 것이 없습니다.

그러나 우리가 왜 이런 식으로하지 않습니까?

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

첫 번째 방법 보다 다소 자연 스럽습니다 . 이것은 Item클래스에 다음과 같은 속성이 있을 때 andvantange을 갖습니다 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인터페이스 라고합시다 .


업데이트 -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
item.AddTo(items)확장 유형 메소드가없는 언어가 있다고 가정하십시오 (자연적이든 아니든). 모든 유형에 대해 addTo를 지원하려면이 메소드가 필요하며 추가를 지원하는 모든 유형의 콜렉션에이를 제공하십시오. 그것은 내가 들어 본 모든 것 사이에 의존성을 도입하는 가장 좋은 예와 같습니다. P-여기서 잘못된 전제는 프로그래밍 추상화를 '실제'삶으로 모델링하려는 것 같습니다. 종종 잘못됩니다.
stijn

1
@ t3chb0t Hehe, 좋은 지적입니다. 처음 사용하는 언어가 어떤 구조가 더 자연스럽게 보이는지 궁금합니다. 나에게는 전혀 자연스럽지 않은 유일한 add(item, collection)것이지만, 좋은 OO 스타일은 아닙니다.
Kilian Foth

3
@ t3chb0t 구어체로, 일을 정상적으로 읽거나 읽을 수 있습니다. "존,이 사과를 당신이 가지고있는 것에 추가하십시오." vs "요한 씨, 사과를 사과에 넣어야합니다." OOP의 세계에서 반사는 의미가 없습니다. 일반적으로 말하는 다른 물체의 영향을 받도록 요구하지 않습니다. OOP에서 가장 가까운 것은 방문자 패턴 입니다. 이것이 표준이 아닌 이유가 있습니다.
Neil

3
이것은 나쁜 생각에 좋은 활을 걸고 있습니다.
Telastyn

3
@ t3chb0t 당신 은 명사 왕국이 흥미로운 읽을 거리를 찾을 수 있습니다 .
paul

답변:


51

아니요, item.AddTo(items)더 자연스럽지 않습니다. 나는 당신이 이것을 다음과 혼합한다고 생각합니다.

t3chb0t.Add(item).To(items)

당신은 바로 items.Add(item)자연 영어에 그리 가깝지 않습니다. 그러나 당신은 또한 item.AddTo(items)자연 영어로 들리지 않습니다 . 일반적으로 목록에 항목을 추가하려고하는 사람이 있습니다. 슈퍼마켓에서 일하거나 요리하고 재료를 넣을 때 사용하십시오.

프로그래밍 언어의 경우, 우리는리스트가 모두 않도록 만들었 : 해당 항목을 저장 하고 그 자체에 추가 할 책임이있는.

접근 방식의 문제점은 항목이 목록이 존재한다는 것을 인식해야한다는 것입니다. 그러나 목록이 전혀 없더라도 항목이 존재할 수 있습니다. 이것은리스트를 인식해서는 안됨을 나타냅니다. 어떤 방식 으로든 코드에서 목록이 발생해서는 안됩니다.

그러나 항목이없는 목록은 존재하지 않습니다 (적어도 쓸모가 없습니다). 따라서 적어도 일반적인 형태로 품목에 대해 알고 있다면 괜찮습니다.


4

@valenterry는 우려의 분리 문제에 대해 매우 옳습니다. 클래스는이를 포함 할 수있는 다양한 컬렉션에 대해 아무것도 몰라 야합니다. 새로운 종류의 컬렉션을 만들 때마다 항목 클래스 를 변경하지 않아도됩니다 .

그건 ...

1. 자바가 아님

Java는 여기서 도움이되지 않지만 일부 언어에는 더 유연하고 확장 가능한 구문이 있습니다.

스칼라에서 items.add (item) 은 다음과 같습니다 :

  item :: items

여기서 :: 는 목록에 항목을 추가하는 연산자입니다. 그것은 당신이 원하는 것을 매우 많이 보입니다. 실제로는 구문 설탕입니다

  items.::(item)

여기서 :: 는 Java의 list.add 와 실질적으로 동일한 스칼라 목록 메소드입니다 . 따라서 첫 번째 버전에서는 item 이 자체를 items에 추가 하는 것처럼 보이지만 실제로는 items 이 추가를 수행합니다. 두 버전 모두 유효한 스칼라

이 트릭은 두 단계로 수행됩니다.

  1. 스칼라에서 연산자는 실제로 메서드 일뿐입니다. Java 목록을 스칼라로 가져 오면 items.add (item)item add item 으로 쓸 수도 있습니다 . 스칼라에서 1 + 2 는 실제로 1. + (2) 입니다. 점과 괄호가 아닌 공백이있는 버전에서 Scala는 첫 번째 객체를보고 다음 단어와 일치하는 메소드를 찾고 (메소드가 매개 변수를 사용하는 경우) 다음 것 (또는 사물)을 해당 메소드로 전달합니다.
  2. 스칼라에서 콜론으로 끝나는 연산자는 왼쪽이 아니라 오른쪽 연결입니다. 따라서 Scala가 메소드를 볼 때 오른쪽에서 메소드 소유자를 찾고 왼쪽의 값을 피드합니다.

많은 연산자에 익숙하지 않고 addTo를 원한다면 Scala는 암시 적 변환이라는 또 다른 옵션을 제공합니다. 두 단계가 필요합니다

  1. 생성자에서 항목 을 가져 오고 주어진 목록에 해당 항목을 추가 할 수 있는 addTo 메소드 가있는 ListItem 이라는 랩퍼 클래스를 작성 하십시오.
  2. 항목 을 매개 변수로 사용하고 해당 항목을 포함 하는 ListItem 을 반환 하는 함수를 만듭니다 . 암시 적으로 표시하십시오 .

경우 암시 적 변환이 가능하고 스칼라는보고있다

  item addTo items

또는

  item.addTo(items)

item에 addTo 가 없으면 암시 적 변환 함수에 대한 현재 범위를 검색합니다. 변환 함수 중 하나가 addTo 메소드 있는 유형을 리턴하면 다음이 발생합니다.

  ListItem.(item).addTo(items)

이제 암시 적 변환이 사용자의 취향에 더 많이 도달한다는 것을 알 수 있지만 주어진 컨텍스트에서 모든 암시 적 변환을 명확하게 인식하지 않으면 코드가 예기치 않은 작업을 수행 할 수 있으므로 위험이 추가됩니다. 이것이 Scala에서이 기능이 더 이상 기본적으로 활성화되지 않는 이유입니다. 그러나 자신의 코드에서 활성화할지 여부를 선택할 수는 있지만 다른 사람의 라이브러리에서는 상태에 대해 아무것도 할 수 없습니다. 용은 여기 있습니다.

콜론으로 끝나는 운영자는 더 나쁘지만 위협하지는 않습니다.

두 접근법 모두 우려 분리 원칙을 유지합니다. 항목 이 수집에 대해 알고있는 것처럼 보이 도록 만들 수 있지만 작업은 실제로 다른 곳에서 수행됩니다. 이 기능을 제공하려는 다른 언어는 최소한주의해야합니다.

2. 자바

구문을 확장 할 수없는 Java에서는 목록에 대해 알고있는 일종의 래퍼 / 데코레이터 클래스를 만드는 것이 가장 좋습니다. 아이템이 목록에 배치 된 도메인에서 아이템을 랩핑 할 수 있습니다. 그들이 그 도메인을 떠날 때 꺼내십시오. 아마도 방문자 패턴이 가장 가깝습니다.

3. tl; dr

스칼라는 다른 언어와 마찬가지로보다 자연스러운 구문을 지원합니다. Java는 못생긴 바로크 패턴 만 제공 할 수 있습니다.


2

확장 메소드를 사용하면 Add ()를 호출해야하므로 코드에 유용한 것이 추가되지 않습니다. addto 메소드가 다른 처리를 수행하지 않은 한 해당 메소드가 존재할 이유가 없습니다. 그리고 기능적인 목적이없는 코드를 작성하는 것은 가독성과 유지 관리 성이 좋지 않습니다.

일반적이지 않은 상태로두면 문제가 더욱 분명해집니다. 이제 다양한 종류의 컬렉션에 대해 알아야 할 항목이 있습니다. 이는 단일 책임 원칙을 위반하는 것입니다. 아이템은 데이터의 홀더 일뿐만 아니라 컬렉션을 조작합니다. 그렇기 때문에 컬렉션에 항목을 추가하는 책임을 두 가지 모두 소비하는 코드 조각까지 유지해야하는 이유입니다.


프레임 워크가 서로 다른 종류의 객체 참조를 인식해야하는 경우 (예 : 소유권을 캡슐화하는 것과 그렇지 않은 객체를 구별하는 것) 소유권을 캡슐화하는 참조는 소유권이있는 컬렉션에 소유권을 양도 할 수있는 수단을 갖는 것이 합리적 일 수 있습니다. 그것을받을 수 있습니다. 각각의 일이 한 명의 소유자로 제한되는 경우 thing.giveTo(collection)[ collection"acceptOwnership"인터페이스를 구현 해야 함] 을 통해 소유권을 이전하는 것은 소유권을 확보 collection한 방법 을 포함하는 것보다 더 의미가 있습니다. 물론 ...
supercat

... Java의 대부분의 것들에는 소유권 개념이 없으며, 객체 참조는 식별 된 객체를 포함하지 않고 컬렉션에 저장 될 수 있습니다.
supercat

1

"add"라는 단어에 집착한다고 생각합니다. "collect"와 같은 대체 단어를 찾아보십시오. 그러면 패턴을 변경하지 않고도 영어에 친숙한 구문을 얻을 수 있습니다.

items.collect(item);

위의 방법은 items.add(item);단어와 다른 단어 선택 만 다른 것과 정확히 동일하며 영어로 아주 자연스럽게 흐릅니다.

때로는 변수, 메소드, 클래스 등의 이름을 바꾸는 것만으로 과세가 패턴을 다시 디자인하는 것을 막을 수 있습니다.


collect항목 모음을 일종의 단일 결과 (또는 모음 일 수도 있음)로 줄이는 메소드의 이름으로 사용되기도합니다. 이 규칙은 Java Steam API에 의해 채택되었으며, 이 문맥에서 이름의 사용은 매우 인기가있었습니다. 나는 당신이 당신의 대답에서 언급 한 문맥에서 사용 된 단어를 본 적이 없습니다. 차라리 add또는put
toniedzwiedz

@toniedzwiedz, push또는 고려하십시오 enqueue. 요점은 구체적인 예제 이름이 아니라 영어 이름 문법에 대한 OP의 요구에 더 가깝다는 사실입니다.
Brian S

1

일반적인 경우에는 그러한 패턴이 없지만 (다른 사람들이 설명하는 것처럼) 유사한 패턴이 장점을 갖는 맥락은 ORM에서 양방향 일대 다 연관입니다.

이러한 맥락에서 당신은 두 기관이의 말을 할 거라고 Parent하고 Child. 부모는 많은 자녀를 가질 수 있지만 각 자녀는 한 부모에 속합니다. 응용 프로그램이 연결을 양쪽 끝에서 탐색 할 수 있어야하는 경우 (양방향), List<Child> children부모 클래스와 Parent parent자식 클래스에 연결 이 있어야합니다 .

협회는 항상 상호 적이어야합니다. ORM 솔루션은 일반적으로로드 된 엔티티에이를 적용합니다. 그러나 응용 프로그램이 엔티티를 조작 할 때 (지속적이거나 새로운) 동일한 규칙을 적용해야합니다. 내 경험상, 당신은 대부분 공용이 Parent.addChild(child)아닌를 호출 하는 메소드를 찾을 것 Child.setParent(parent)입니다. 후자에서는 일반적으로 예제에서 구현 한 것과 동일한 검사를 찾습니다. 그러나 다른 경로도 구현할 수 있습니다 : Child.addTo(parent)which calls Parent.addChild(child). 상황에 따라 가장 다른 경로가 다릅니다.


1

자바에서 일반적인 컬렉션을 보유하지 않는 것을 보유하고 --it 참조 것들, 또는 더 정확하게 사본참조 것들입니다. 대조적으로 도트 멤버 구문은 일반적으로 참조 자체가 아닌 참조로 식별되는 것에 작용하는 데 사용됩니다.

사과를 그릇에 넣으면 사과의 일부 측면 (예 : 위치)이 바뀌지 만 그릇에 "개체 # 29521"이라는 종이를 넣으면 사과가 어떤 식 으로든 바뀌지 않습니다. 객체 # 29521. 또한 콜렉션에는 참조 사본이 들어 있기 때문에 "개체 # 29521"이라고 적힌 종이를 그릇에 넣는 것과 동등한 Java는 없습니다. 대신에, # 29521 번호를 새로운 종이에 복사하고 그것을 그릇에 보여 주면 (제공하지 않음), 그들은 자신의 사본을 만들어 원본에 영향을 미치지 않습니다.

실제로, Java에서 객체 참조 ( "종이 표")를 수동적으로 관찰 할 수 있기 때문에, 참조 나 이에 의해 식별 된 객체는 그것들로 무엇을하고 있는지 알 수있는 방법이 없습니다. 컬렉션의 존재를 전혀 알지 못하는 개체에 대한 참조를 여러 개 보유하는 대신 컬렉션에 캡슐화 된 개체와 양방향 관계를 설정하는 컬렉션이 있습니다. 이러한 컬렉션 중 일부에서는 메서드에 컬렉션에 추가하도록 요청하는 것이 좋습니다 (특히 개체가 한 번에 하나의 컬렉션에만있을 수있는 경우). 그러나 일반적으로 대부분의 컬렉션은 해당 패턴에 맞지 않으며 해당 참조로 식별 된 개체의 부분에 관계없이 개체에 대한 참조를 허용 할 수 있습니다.


이 게시물은 읽기 어렵습니다 (텍스트의 벽). 더 나은 형태로 편집 하시겠습니까 ?
gnat

@ gnat : 그게 더 낫니?
supercat

1
@gnat : 죄송합니다. 네트워크 문제로 인해 수정 사항을 게시하지 못했습니다. 당신은 지금 그것을 더 좋아합니까?
supercat

-2

item.AddTo(items)수동적이기 때문에 사용되지 않습니다. Cf "항목에 목록이 추가됨"및 "방이 장식자가 그린다"

패시브 구조는 괜찮지 만 적어도 영어로는 적극적 구조를 선호합니다.

OO 프로그래밍에서 패러다임은 행위자가 아닌 행위자에게 두드러지게 나타납니다 items.Add(item). 목록은 무언가를하는 것입니다. 배우이기 때문에 이것이 더 자연스러운 방법입니다.


당신이에 대해 같은 말할 수 - 상대성 이론 - 그것은 어디 ;-) 서에 따라 list.Add(item) 항목이 목록에 추가되고 또는 목록에 항목을 추가 또는에 대한 item.AddTo(list) 항목이 목록에 자신을 추가 또는 I 항목을 추가 목록에 같은 또는 당신이 말한 항목이 목록에 추가됩니다 - 그들 모두가 정확합니다. 문제는 누가 배우이고 왜 냐는 것입니다. 항목은 또한 배우이며 그룹 이이 항목을 갖기를 원하지 않는 그룹 (목록) 에 참여 하려고합니다. ;-) 항목이 그룹에있는 것으로 활동합니다.
t3chb0t

배우는 활동적인 일을하는 것입니다. "원하다"는 수동적 인 것입니다. 프로그래밍에서 항목이 추가 될 때 변경되는 목록이므로 항목이 전혀 변경되지 않아야합니다.
매트 엘렌

... Parent물건 이있을 때처럼 종종 그렇습니다. 분명히 영어는 모국어가 아닐 수도 있지만 내가 원 하거나 그가 원하는 것을하지 않는 한 내가 원하는 것은 수동적이지 않습니다 .;-]
t3chb0t

무언가를 원할 때 어떤 행동을합니까? 그게 내 요점이야 문법적으로 수동적 일 필요 는 없지만 의미 적으로는 수동적입니다. WRT 부모 특성은 예외이지만 모든 휴리스틱에는 예외가 있습니다.
매트 엘렌

그러나 List<T>(적어도에있는 것 System.Collections.Generic)은 Parent속성을 전혀 업데이트하지 않습니다 . 그것이 일반적인 것이면 어떻게 될 수 있습니까? 내용물을 추가하는 것은 일반적으로 컨테이너의 책임입니다.
Arturo Torres Sánchez
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.