Как сделать шаблон внутреннего интерфейса? - PullRequest
2 голосов
/ 14 июня 2011

У меня есть много классов (с разными базовыми классами и тому подобное), которые имеют общий интерфейс.

public interface IObjectWithSector
{
   Sector Sector {get;}
}

Но у моего базового класса Sector (который определен в той же сборке, что и интерфейс, и у всех реализующих базовых классов) есть открытый метод void AddObject(IObjectWithSector obj);, который, помимо прочего, должен содержатьobj.Sector = this; инструкция.Это представляет проблему.Если я сделаю сеттер общедоступным в интерфейсе, все потребительские классы смогут установить Sector и, таким образом, обойти логику внутри моего метода AddObject.Но если я оставлю это вне моего открытого интерфейса, даже Сектор не увидит сеттер.Я думаю о «внутреннем интерфейсе», но эти вещи не очень хороши, так как я не могу наследовать общедоступный от них, так что в любом случае это будет перекрестное приведение.

Каков наилучший шаблон для этого?

Ответы [ 5 ]

5 голосов
/ 14 июня 2011

Вместо предоставления интерфейса предоставьте базовому классу свойство внутреннего установщика. Все потребители должны быть производными от этого класса.

Вы не можете заставить клиентов каким-то образом реализовать то, что может видеть только ваша сборка. С точки зрения клиентов, это не имеет особого смысла. Если они необходимы для реализации вашего сеттера, они также смогут вызвать его. В конце концов, это их код в сборке.

Реализация базового класса, однако, предоставляется вами и, таким образом, может иметь внутренних членов. Клиенты могут расширять его, но вам гарантировано, что внутреннее содержимое есть, и что только вы можете видеть его.

1 голос
/ 14 июня 2011

Вы запрашиваете что-то с интерфейсом, доступным только для чтения, и в зависимости от того, что оно также доступно для записи.По сути, это слабая абстракция, которая может сбить пользователей с толку, даже если вы обойдете ее, приведя к внутреннему интерфейсу.

Если это приемлемо, вы можете решить эту проблему, сделав свой класс Sector фабрикойдругие объекты:

public class ConcreteObjectWithSector: IObjectWithSector {
    private Sector sector;
    internal ConcreteObjectWithSector(Sector sector) {
        this.sector = sector;
    }
    // everything else
}

public class Sector {
    public ConcreteObjectWithSector CreateConcreteObjectWithSector() {
        var obj = new ConcreteObjectWithSector(this);
        this.stuff.Add(obj);
        // Other logic
        return obj;
    }
}

У вас нет единого метода, который мог бы обработать всех желающих, но вы знаете, что инварианты ваших объектов удовлетворены и не должны подвергать риску вашу абстракцию.

0 голосов
/ 12 мая 2015

Кажется, вы пытаетесь добавить произвольные данные (экземпляр Sector) к объектам вашего потребителя, к которым они (ваши потребители) не должны иметь доступ или изменить их.

Существует способ сделать это чисто (фактически двумя способами), не подвергая себя опасности, даже не заставляя потребителей внедрять ваш интерфейс.

Первый способ - сохранить Dictionary<object, Sector>, который будет содержать Sector для объекта каждого потребителя. Но этот способ бесполезен, поскольку вы не знаете, когда удалять объект из словаря, поскольку вы не управляете временем жизни объекта потребителя. Это также предотвратит сбор мусора на объектах потребителей.

Второй (правильный) способ - использовать ConditionalWeakTable<object, Sector>, который был создан именно для этой цели - прикрепление произвольных данных к объектам, не влияя на их статус с корнем GC. Пока объект потребителя «жив» (не собранный мусор), вы всегда можете прочитать его сектор, и запись удаляется, когда объект собирается мусором.

0 голосов
/ 14 июня 2011

Почему вы явно не реализуете внутренний интерфейс:

public interface IObjectWithSector
{
    Sector Sector { get; }
}

internal interface IObjectWithSectorSetter: IObjectWithSector
{
    void SetSector(Sector sector);
}

Теперь вы можете сделать:

public ObjectWithSector: IObjectWithSectorSetter
{
    public Sector Sector { get { ... } }
    void IObjectWithSectorSetter.SetSector(Sector sector) { ... }
}

Я не вижу, чтобы это становилось грязным.

РЕДАКТИРОВАТЬ : Изменение порядка наследования интерфейсов, как было раньше, приводило к ошибке компиляции несогласованного доступа.

0 голосов
/ 14 июня 2011

можно смешать публичный гет с внутренним набором но это становится грязным, так как внутреннее получение должно использовать общественное получение и один из них нуждается в явном объявлении (поскольку get и set не являются отдельными, а частью одного свойства) ИМО чистое решение состоит в том, чтобы использовать метод установки

internal void SetSector(Sector sector);

также обратите внимание, что внутренний интерфейс может наследовать открытый интерфейс

internal interface IPrivate : IPublic { }
private IPrivate blah = ...
public IPublic Blah { get { return blah; } }

работает просто отлично (это также может работать с общей ковариацией в точечной сети 4.0 для коллекций и т. д.)

...