Создание сквозного кода, когда "предпочтительнее композиции по наследованию"
Проблема
Скажем, я пытаюсь моделировать мобильный телефон как комбинацию обычного телефона и КПК. Это своего рода сценарий множественного наследования (сотовый телефон - это телефон, и это КПК). Поскольку С# не поддерживает множественное наследование, это в значительной степени требует некоторой композиции. Кроме того, позвольте сказать, что у меня есть другие причины, чтобы поддержать композицию в любом случае.
Я всегда задаюсь вопросом: есть ли какие-либо инструменты, которые автоматически генерируют весь неизбежный сквозной код?
Позвольте мне изложить мой пример с каким-то фактическим кодом:
Интерфейсы:
public interface IPhone
{
public void MakeCall(int phoneNumber);
public void AnswerCall();
public void HangUp();
}
public interface IPda
{
public void SendEmail(string[] recipientList, string subject, string message);
public int LookUpContactPhoneNumber(string contactName);
public void SyncWithComputer();
}
Реализации:
public class Phone : IPhone
{
public void MakeCall(int phoneNumber) { // implementation }
public void AnswerCall() { // implementation }
public void HangUp() { // implementation }
}
public class Pda : IPda
{
public void SendEmail(string[] recipientList, string subject, string message) { // implementation }
public int LookUpContactPhoneNumber(string contactName) { // implementation }
public void SyncWithComputer() { // implementation }
}
Класс CellPhone
public class CellPhone : IPhone, IPda
{
private IPhone _phone;
private IPda _pda;
public CellPhone(IPhone phone, IPda pda)
{
_phone = phone;
_pda = pda;
}
public void MakeCall(int phoneNumber)
{
_phone.MakeCall(phoneNumber);
}
public void AnswerCall()
{
_phone.AnswerCall();
}
public void HangUp()
{
_phone.HangUp();
}
public void SendEmail(string[] recipientList, string subject, string message)
{
_pda.SendEmail(recipientList, subject, message);
}
public int LookUpContactPhoneNumber(string contactName)
{
return _pda.LookUpContactPhoneNumber(contactName);
}
public void SyncWithComputer()
{
_pda.SyncWithComputer();
}
}
Написание класса CellPhone является утомительным и подверженным ошибкам:
Весь этот класс действительно действует как канал для классов Phone
и Pda
. На самом деле нет никаких оснований для того, чтобы человеческие усилия требовались для ввода всех этих сквозных утверждений (например, _phone.MakeCall(phoneNumber);
). Он просто раскрывает открытый интерфейс полей нескольких членов.
Вопросы
-
Есть ли инструмент (желательно свободный:)), который спасет меня от ошибок, связанных с ошибкой написания сквозных методов? Я знаю, что я могу автоматически генерировать заглушки с помощью VS, но это только помогает мне на полпути.
-
Можете ли вы оценить целесообразность и желательность такой функции? Стоит ли предлагать Microsoft добавить такой тип генерации кода? Не могли бы вы его перенести, если бы я это сделал? Если нет, какие у вас есть возражения?
Edit
Кажется, что все говорят одно и то же: почему бы мне просто не сделать _phone
и _pda
в общедоступные свойства? Мое возражение против этого заключается в том, что оно нарушает "принцип наименьшего знания". Клиент моего класса CellPhone
должен просто делать то, что делает сотовый телефон, ему не нужно иметь дело с выяснением, какие функции являются Phone
функциями и которые являются Pda
функциями. Это создает дополнительные зависимости и делает возможности интерфейса CellPhone менее очевидными.
Кроме того, не сосредотачивайтесь только на этом примере. Что, если я пишу адаптер? Он может содержать несколько интерфейсных элементов, которые являются просто сквозными, но также могут содержать некоторые уникальные элементы с уникальной реализацией. Я считаю, что есть много случаев, когда сквозной код - это хорошо, мне просто не нравится писать его.
Ответы
Ответ 1
Да, вы можете сгенерировать методы, используя блестящую надстройку VS, ReSharper
Выберите параметр generate delegating methods" из меню генерации кода (Alt-Insert с помощью схемы ярлыков по умолчанию).
Если у вас есть класс, который реализует интерфейс, содержащий поле, которое реализует один и тот же интерфейс, R # даст вам возможность генерировать код сквозной передачи. Он также может работать с любым количеством интерфейсов.
Ответ 2
Я видел это предложение раньше (не обязательно для). Это было давно. Я считаю, что автор создал проблему со связью, хотя я не знаю ее статуса.
Предлагаемый синтаксис был одним из следующих:
public class CellPhone : IPhone, IPda
{
private readonly IPhone _phone implements IPhone;
private readonly IPda _pda : IPda;
// ...
}
Ответ 3
Добавление некоторых визуальных эффектов и деталей к ранее предоставленному ответу.
-
добавить интерфейс к классу, который вы хотите быть классом упаковки
class MyWebElement : IWebElement { }
- Найти/нажать "Делегировать реализацию" YourInterfaceHere "в новое поле
![Реализация делегата]()
- Выберите свои параметры
![Параметры делегата]()
-
Нажмите "Готово" и наслаждайтесь новым классом
class MyWebElement : IWebElement
{
private IWebElement _webElementImplementation;
public IWebElement FindElement(By @by)
{
return _webElementImplementation.FindElement(@by);
}
public ReadOnlyCollection<IWebElement> FindElements(By @by)
{
return _webElementImplementation.FindElements(@by);
}
public void Clear()
{
_webElementImplementation.Clear();
}
public void SendKeys(string text)
{
_webElementImplementation.SendKeys(text);
}
public void Submit()
{
_webElementImplementation.Submit();
}
public void Click()
{
_webElementImplementation.Click();
}
public string GetAttribute(string attributeName)
{
return _webElementImplementation.GetAttribute(attributeName);
}
public string GetCssValue(string propertyName)
{
return _webElementImplementation.GetCssValue(propertyName);
}
public string TagName
{
get { return _webElementImplementation.TagName; }
}
public string Text
{
get { return _webElementImplementation.Text; }
}
public bool Enabled
{
get { return _webElementImplementation.Enabled; }
}
public bool Selected
{
get { return _webElementImplementation.Selected; }
}
public Point Location
{
get { return _webElementImplementation.Location; }
}
public Size Size
{
get { return _webElementImplementation.Size; }
}
public bool Displayed
{
get { return _webElementImplementation.Displayed; }
}
}
Ответ 4
Вы можете проверить эту статью, которая входит в версию псевдо-множественного наследования в С#.
Ответ 5
Интересный вопрос. Может ли класс CellPhone действительно полагаться на реализацию методов IPhone, предоставляемых существующим, конкретным классом IPhone? Или более вероятно, что CellPhone понадобится какой-то пользовательский код, поддерживающий эти методы?
Глядя на ваш код (хотя я мало знаю о ваших требованиях) заставляет меня писать что-то вроде этого:
public class CellPhone
{
private Phone _phone;
private Pda _pda;
public CellPhone(Phone phone, Pda pda)
{
_phone = phone;
_pda = pda;
}
public IPhone getPhone()
{
return _phone;
}
public IPda getPda()
{
return _pda;
}
public void MakeCall(string contactName) {
int phoneNumber = _pda.LookUpContactPhoneNumber(contactName);
_phone.MakeCall(phoneNumber);
}
}
Вопрос 2:
Мне не нравится автоматический код. Даже если машина генерирует его - и без ошибок - вы по-прежнему несете ответственность за его поддержание. Эти методы кажутся таким количеством шума.