Это плохой дизайн, чтобы передать содержащий объект в качестве аргумента метода содержащегося объекта? - PullRequest
2 голосов
/ 03 января 2012

Плохо ли передавать объект-объект в качестве аргумента методу объекта-объекта, как в приведенном ниже упрощенном примере?

class A
{
    private B _containedObject;

    public A(B b)
    {
            _containedObject = b;
            //...
    }

    public void SomeMethod()
    {
            //...
            _containedObject.SomeMethod(this);
            //...
    }
}

class B
{
    public void SomeMethod(A a)
    {
            //do something with a
    }
}

P.s. Приведенный выше пример упрощен, чтобы проиллюстрировать лишь отношение удержания и передачу содержащего объекта в содержащий объект, и сам по себе не иллюстрирует цель этого. Будьте уверены, что есть цель.

Ответы [ 6 ]

2 голосов
/ 03 января 2012

в этом простом примере это выглядит как форма двойной отправки. в этом случае я бы передал A в качестве аргумента метода B.

void SomeMethod(A a)
{
    a.DoSomething(this);
}

однако, это только один пример. В других случаях передача объекта в качестве аргумента ctor может быть более полезной.

1 голос
/ 04 января 2012

Возможно, наиболее распространенной причиной использования этой техники является шаблон посетителя. Я ужасно долго нахожу пример, который мне нравится в Интернете, поэтому я просто использую диаграмму классов из статьи Википедии. Посмотрите на эту диаграмму: http://upload.wikimedia.org/wikipedia/commons/5/59/VisitorPatternUML.png

Теперь изображение автомобиля, принимающего объект посетителя:

Engine myEngine;
CarElementPrintVistor myVisitor;
myEngine.accept(myVisitor);

Но внутри метода accept () автомобильная часть поворачивается вправо и передает себя посетителю!

myVisitor.visit(this)

Проверьте шаблон проектирования Visitor для получения дополнительной информации.

1 голос
/ 04 января 2012

В некоторых случаях вам может потребоваться передать содержащий объект в качестве параметра в содержащийся объект. Как отмечали другие авторы, это называется «двойная отправка». Язык Smalltalk предоставляет классический пример.

Пример касается сложения целых чисел и чисел с плавающей запятой (или других типов чисел). В C ++ или Java ваш целочисленный класс будет иметь полиморфные методы, такие как:

Integer {
     add(Float) {...}
     add(Integer) {...}
     add(UserDefinedType) {...}
}

Каждый метод добавления может реализовывать различные виды корректного добавления. Компилятор использует информацию о типе, чтобы выбрать правильный метод. Оператор Integer (6) .add (Float (1.0)) будет вызывать метод Integer :: add (Float).

Smalltalk, в отличие от C ++ и Java, динамически типизирован. Класс Smalltalk может иметь только один метод с именем add .

Integer :: добавить (Object)

Компилятор не знает класс «объект». По этой причине Smalltalk не может реализовать add так же, как C ++ или Java. Вместо этого Smalltalk использует двойную диспетчеризацию. Это работает так:

   Integer {
          add(Object o) {
              return o.addToInteger(this)
          }        
    }

Другие классы чисел, такие как Float, Fraction и даже Integer, реализуют метод addToInteger ().

Float {
      addToInteger(Object o) {
          //Perform float + in math
          return result
     }
}

Двойная диспетчеризация решает проблему того, как полиморфно реализовывать операции человека на языке с динамической типизацией. Но есть более распространенное использование: шаблон посетителей. Я помещу это в отдельный ответ.

1 голос
/ 03 января 2012

Если родительский объект объекта существует и известен во время создания объекта, и у объекта никогда не будет другого родителя, часто полезно, чтобы каждый дочерний объект сохранял ссылку на родительский объект при его создании.Затем можно будет попросить дочерний объект сделать что-либо с участием его родителя, не передавая родительский объект в качестве аргумента, поскольку ребенок уже будет знать, кто его родитель.

Одна из сложностей, связанных с этим понятием, заключается в том, чтов зависимости от того, что делают классы, можно разрешить объектам родительского типа ограничивать, кто может создавать объекты с ними в качестве родительских.Если родительский и дочерний типы будут в одной сборке, это легко сделать с помощью модификатора internal в дочернем конструкторе.Если кто-то хочет разрешить возможность того, что родительский объект может принадлежать классу, определенному в еще неписаной сборке, но предоставить таким родительским объектам гарантию времени компиляции, что они не будут «присоединены» без их «согласия»,Немного сложнее, но, вероятно, это можно сделать достаточно разумно, используя приватный конструктор и метод общедоступной фабрики:

public interface IAssuredParent<T>
static childClass createAttachedChild<T,U>(T parent) where T:IAssuredParent<U>
{...};

Если родитель определяет P закрытый класс XYZ и реализует IAssuredParent<XYZ>, онбудет единственным классом, имеющим доступ к типу XYZ, и, таким образом, единственным классом, который может успешно вызвать createAttachedChild с парой параметров типа, которые могут вместить фактический параметр типа P и соответствовать ограничениям типа.

1 голос
/ 03 января 2012

Нет, это неплохой дизайн и используется довольно часто.Вы, однако, должны быть осторожны, чтобы не вызвать утечку памяти, если вы делаете это.Когда вы передаете объект-контейнер внутреннему объекту, он будет хранить ссылку на родительский объект до тех пор, пока не будет удален внутренний объект.

Это означает, что если вы создадите новую букву A, и она сообщит о новостях ab, если вы позволите всемдругие ссылки истекают A будет по-прежнему рядом, потому что B держит копию.

0 голосов
/ 03 января 2012

На мой взгляд, это не очень хороший дизайн, чтобы его улучшить, вы можете объявить интерфейс вашего объекта A и объявить ваш SomeMethod после:

void SomeMethod(IMyInterface a)

тогда ваш класс A будетвыглядит так:

public class A : IMyInterface
{
  ....
}

Таким образом, этот метод даст абстракцию в реализации.

В целом лучший дизайн == меньше отношений.

...