2010-06-18 3 views
1

Проект C#, но он может применяться к любым языкам OO. 3 интерфейсов взаимодействующие:OOP, дизайн интерфейса и инкапсуляция

public interface IPublicData {} 

public /* internal */ interface IInternalDataProducer { string GetData(); } 

public interface IPublicWorker { 
    IPublicData DoWork(); 
    IInternalDataProducer GetInternalProducer(); 
} 

public class Engine { 
    Engine(IPublicWorker worker) {} 
    IPublicData Run() { 
     DoSomethingWith(worker.GetInternalProducer().GetData()); 
     return worker.DoWork(); 
    } 
} 

Ясно Engine параметрический в фактическом работнике, который делает работу. Другим источником параметризации является то, как мы производим «внутренние данные» через IInternalDataProducer. Эта реализация требует, чтобы IInternalDataProducer был общедоступным, поскольку он является частью декларации открытого интерфейса IPublicWorker. Тем не менее, я бы хотел, чтобы он был внутренним, поскольку он используется только движком.

Решение состоит в том, чтобы сделать внутренние данные сами, но это не очень элегантно, так как существует всего несколько способов его создания (в то время как есть много других реализаций рабочих), поэтому приятно делегировать пару отдельные конкретные классы. Кроме того, IInternalDataProducer используется в других местах внутри двигателя, поэтому для двигателя полезно обходить фактический объект.

Также не вариант (очевидно) передать экземпляр IInternalDataProducer непосредственно на конструктор Engine. Мы не хотим, чтобы пользователи библиотеки (Engine) знали о производителе данных. Обязанность работника - объявлять (производить), какой производитель данных следует использовать.

Я ищу элегантный, безопасный тип, идеи/узоры. Приветствие :-)

EDIT: На основании ответа Джеффа, вот решение:

Это не идеально, потому что интерфейс по-прежнему «грязный» с GetData(), которые пользователь не должен видеть (но мой оригинальный интерфейс тоже был грязным), а потому, что он приводит к «дублированию интерфейса» (GetData() объявлен в двух интерфейсах) - у вас не может быть всего.

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

public interface IPublicData {} 

internal /* ! */ interface IInternalDataProducer { string GetData(); } 

public interface IPublicWorker { 
    IPublicData DoWork(); 
    string GetData(); 
} 

public class APublicWorker : IPublicWorker { 

    private IInternalDataProducer dataProducer; 

    public APublicWorker() { 
     dataProducer = new SomeDataProducer(); 
    } 

    IPublicData DoWork() { ... } 
    string GetData() { 
     /* Delegation! */ 
     return dataProducer.GetData(); 
     /* ********* */ 
    } 
} 

public class Engine { 
    Engine(IPublicWorker worker) {} 
    IPublicData Run() { 
     DoSomethingWith(worker.GetData()); 
     return worker.DoWork(); 
    } 
} 

ответ

0

Вы можете решить эту проблему путем инкапсуляции производителя данных в рабочий :

public interface IPublicWorker { 
    IPublicData DoWork(); 
    // Callers don't care how a worker gets this data 
    string GetData(); 
} 

звонок в двигателе будет выглядеть следующим образом:

IPublicData Run() { 
    DoSomethingWith(worker.GetData()); 
    return worker.DoWork(); 
} 
+0

Вам не хватает члена IPublicWorker внутри движка, но nit-picks в сторону, именно это я и рекомендовал бы. –

+0

Спасибо, Джефф. К сожалению, это именно то, чего я пытаюсь избежать. Мы не хотим, чтобы пользователи библиотеки (движка) знали о производителе данных. Обязанность работника - объявлять (производить), какой производитель данных следует использовать, и это должно быть прозрачным для пользователя. – Mau

+1

@Mau - Я понимаю, вы пытаетесь избежать этого, но я пытаюсь убедить вас, что это будет плохой идеей! Мы могли бы немного изменить ситуацию, чтобы придать производителю данных вместо рабочего (вместо этого это звучит как не тот владелец, учитывая то, что вы описали), но это просто переносит проблему на рабочего. –

-2

Почему бы не определить GetInternalProducer(), как

object GetInternalProducer(); 

и использовать что-то вроде

IInternalDataProducer producer = GetInternalProducer() as IInternalDataProducer. 

вам нужно будет проверить нулевой указатель, но вам не нужно xpose IInternalDataProducer больше.

Таким образом, вы можете предоставить фабрику для создания объектов IInternalProducer внутри Engine и связать их с объектами IPublicWorker или любым другим кодом, находящимся вне приложения. В противном случае клиентский код должен знать о IInternalDataProducer, тем самым нарушая инкапсуляцию Engine.

+0

это не решает описанные проблемы, но добавляет тип не-безопасности и без необходимых допущений. – Restuta

+0

Ну, я ищу тип безопасного типа. – Mau

+0

Если внутренний продюсер связан с общественным работником, но работники не заботятся об этом, это способ сохранить конфиденциальность IInternalDataProducer. У двигателя может быть фабрика для создания объектов, которые затем могут быть переданы обратно в двигатель и отброшены в подходящее место. В решении, которое вы разместили, клиентский код должен все еще знать о IInternalDataProducer, который подразумевает, что единственным возможным клиентским кодом является сам код двигателя. Безопасность типа действительно должна применяться во время выполнения, но это вполне подходит в парадигме C#. –

0

Edited с учетом требований от комментариев:

public interface IPublicData { } 

public interface IDataProducer { string GetData(); } 

internal interface IInternalDataProducer : IDataProducer { string GetData(); } 

internal class InternalDataProducer : IInternalDataProducer 
{ 
    public string GetData() 
    { 
     throw new NotImplementedException(); 
    } 
} 

public interface IPublicWorker 
{ 
    IPublicData DoWork(); 
    IDataProducer GetInternalProducer(); 
} 

class PublicWorker : IPublicWorker 
{ 
    public IPublicData DoWork() 
    { 
     throw new NotImplementedException(); 
    } 

    public IDataProducer GetInternalProducer() 
    { 
     return new InternalDataProducer(); //here you binds PublicWorker to paricular data provider 
    } 
} 


public class Engine 
{ 
    private IPublicWorker worker; 

    public Engine(IPublicWorker worker) 
    { 
     this.worker = worker; 
    } 

    IPublicData Run() 
    { 
     DoSomethingWith(this.worker.GetInternalProducer()); 
     return worker.DoWork(); 
    } 

    private void DoSomethingWith(IDataProducer getData) 
    { 
     throw new NotImplementedException(); 
    } 
} 
+0

Спасибо Restuta. Это на самом деле не решает проблему, потому что 0)) пользователь Engine должен знать о внутренних производителях данных, которые я пытаюсь устранить. 2) Ответственность IPublicWorker заключается в том, чтобы объявить, что IInternalDataProducer следует использовать для сбора данных (каждая рабочая версия может выбрать одного из немногих конкретных производителей). – Mau

+0

Я буду обновлять свой ответ в соответствии с вашими замечаниями. – Restuta

+0

Кажется, вы пытаетесь достичь статической привязки. Я имею в виду статически привязать IPublicWorker к Paticular DataProvider, как сделать это динамически? Вы создадите общий интерфейс IDataProducer, и каждая ваша реализация paticular IPublicWorker будет знать, какой наследник (IInternalDataProducer) в этом случае он должен использовать? – Restuta

0

UPDATE
Мое решение не кажется правильным, по крайней мере, это не работает, как я предназначен. Может быть, кто-то другой может заставить его работать, так как это будет наиболее логичное решение (imho).


позволяет внутреннему интерфейсу наследовать от открытого интерфейса. Это позволяет внешним частям получать доступ только к тому, что вы действительно хотите получить, оставаясь при этом скрытыми внутренними частями.

public interface IPublicData {} 

public interface IPublicDataProducer {} 
internal interface IInternalDataProducer : IPublicDataProducer { string GetData(); } 

public interface IPublicWorker { 
    IPublicData DoWork(); 
    IPublicDataProducer GetProducer(); 
} 

public class Engine { 
    Engine(IPublicWorker worker) {} 
    IPublicData Run() { 
     DoSomethingWith(worker.GetProducer()); 
     return worker.DoWork(); 
    } 
} 

Это позволяет разделять данные для внешних и внутренних пользователей ваших интерфейсов.

+0

Да! Это близко к тому, что я хотел бы. Но все же вам нужен листинг внутри DoSomethingWith (от IPublicDataProducer до IInternalDataProducer). – Mau

+0

Хмм, кажется, это дополнительное требование =) – Restuta

+0

Если вам нужно бросить в CONCRETE IInternalDataProducer с типом двигателя, чем мне не хватает чего-то, потому что вы говорили: «Мы не хотим, чтобы пользователи библиотеки (Engine) знали о производители данных. " – Restuta

0

На самом деле решение Олега не так уж плохо, но его можно улучшить.

Учитывая, что вы хотите установить ограничения на интерфейс IPublicWorker, я предполагаю, что вы хотите управлять реализациями IPublicWorker и предоставлять своим пользователям определенный API для их получения. Если это так, то вы могли бы вывести IInternalPublicWorker из IPublicWorker, и в конструкторе двигателя, убедитесь, что IPublicWorker действительно ожидаемого типа:

public interface IPublicData {} 


public internal interface IInternalDataProducer { string GetData(); } 

public interface IPublicWorker { 
    IPublicData DoWork(); 
} 

public internal interface IInternalPublicWorker : IPublicWorker { 
    IInternalDataProducer GetInternalProducer(); 
} 

public class Engine 
{ 
    IInternalPublicWorker _worker; 

    Engine(IPublicWorker worker) 
    { 
     if (!(worker is IInternalPublicWorker)) 
     { 
      throw new InvalidOperationException("We don't support workers that were not obtained from our library."); // add some helpful message about which method to use to obtain a worker 
     } 
     _worker = (IInternalPublicWorker)worker; 
    } 
    IPublicData Run() 
    { 
     DoSomethingWith(_worker.GetInternalProducer().GetData()); 
     return _worker.DoWork(); 
    } 
} 
Смежные вопросы