Как написать метод, который может совместно использоваться двумя не наследуемыми классами - PullRequest
3 голосов
/ 17 октября 2011

У меня есть 2 класса, оба имеют одинаковый метод (имя + тип + поведение) и одно и то же свойство (имя + тип)

public class Country
{
    public string Name { get; set; }

    public void DisplayName()
    {
        Console.WriteLine(this.Name);
    }
}

public class Person
{
    public string Name { get; set; }

    public void DisplayName()
    {
        Console.WriteLine(this.Name);
    }
}

- Person и Country классам не разрешено наследовать

В приведенном выше коде вы можете видеть, что класс Person имеет похожий метод (DisplayName), подобный Country class. Я ищу способ, чтобы оба класса могли использовать одни и те же коды методов, я хочу сделать это, потому что в моих реальных кодах метод, которым я хочу поделиться, очень велик, и всякий раз, когда я изменяю код в одном классе, я должен копировать вставить это в другом классе тоже. Я чувствую, что это не правильный путь.

Подскажите, пожалуйста, как решить эту проблему.

Ответы [ 10 ]

2 голосов
/ 17 октября 2011

Вы говорите, что они не могут наследовать от общего базового класса, но вы могли бы добавить интерфейс, верно? Я предлагаю дать им общий интерфейс . Затем определите метод расширения для этого интерфейса. Метод появится для каждого из них в VS.

( Предположение : это будет работать, если члены класса, к которым обращаются методы расширения, являются открытыми или внутренними.)

interface IDisplayable
{
    string Name {get; set;}
}

public class Country : IDisplayable
{
    public string Name { get; set; }
}

public class Person : IDisplayable
{
    public string Name { get; set; }
}

public static void DisplayName(this iDisplayable d)
{
    return doSomeDisplayLogic(d.Name);
}

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

Я не знаю, если вы новичок в методах расширения или нет. Они очень мощные. (И, как и многие мощные функции, ими можно злоупотреблять). Метод расширения для интерфейса поначалу кажется безумным, пока вы не поймете, как на самом деле работают методы расширения. LINQ не будет работать без этого!

Обновление : я вижу ваш комментарий выше о том, что классы не могут наследовать от общего класса, потому что они уже наследуются от общего класса (который, я полагаю, не может быть слишком запутан) , Я хотел бы указать на Вариант 2 , основанный на этом: Создание нового класса, который Country / Person / etc. будет наследовать от того, что само наследует от существующего общего родительского класса. Существующий базовый класс станет, так сказать, прародителем . Это стало бы большим маршрутом, если Страна и Персона имеют другие общие характеристики помимо этого метода DisplayName. Если DisplayName - это все, что вам нужно, шаблон интерфейса / расширения может быть лучше.

1 голос
/ 17 октября 2011

Определить интерфейс

public interface INameable
{
    string Name {get;}
}

затем добавьте расширение

public static class INameableExt
{
    public static void DisplayName(this INameable n)
    {
        // do your thing
    }
}
1 голос
/ 17 октября 2011

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

Как более общее решение OOD, я бы предложил выделить это поведение в отдельный класс обслуживания, абстрагированный интерфейсом:

public interface IDisplayService()
{
    void Display();
}

Затем реализуйте его и введите в оба класса через конструктор.

Кроме того, вместо введения интерфейсов и новых классов вы можете ввести Action или Func<> через конструктор или даже свойство, а затем вызвать этот метод, вызвав внедренный в делегат.

0 голосов
/ 17 октября 2011

предупреждение: здесь много непроверенного кода, в основном дикие догадки, поскольку я не согласен с базовым предположением «нет наследования».

что-то вроде этого должно помочь вам. создайте новый статический класс и вставьте сюда свой код.

public static class Display
{
    public static void DisplayName<T>(T obj)
    {
        if ((T is Person) || (T is Country) || (T is whateveryouwant))
        {
            //do stuff
        }
    }
}

в ваших классах рефакторинг ShowDisplayName () для вызова этого с "this" в качестве параметра.

... public void DisplayName () { Выводимое (это); } ...

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

0 голосов
/ 17 октября 2011

Вы можете использовать состав: определить интерфейс, класс, который его реализует, и затем Person и Country реализовать интерфейс, вызвав методы класса реализации:

// the interface
public interface IName {
    string Name { get; set; }
    void DisplayName();
}

// a class that implements the interface with actual code
public class NameImpl : IName {
    public string Name { get; set; }

    public void DisplayName() {
        Console.WriteLine(this.Name);
    }
}

public class Country : IName {

    // instance of the class that actually implements the interface
    IName iname = new NameImpl();

    // forward calls to implementation
    public string Name {
        get { return iname.Name; }
        set { iname.Name = value; }
    }

    public void DisplayName() {
        // forward calls to implementation
        iname.DisplayName();
    }
}
0 голосов
/ 17 октября 2011

Я думаю, что вы просите, это наследование нескольких классов, которое не разрешено в C #. (но может быть с C ++, который вы НЕ делаете).

Все остальные определили решение ИНТЕРФЕЙСА, и, вероятно, лучший путь. Однако, из вашего описания, у вас есть ОДИН БЛОК кода, который идентичен независимо от типа объекта, являющегося человеком или бизнесом. И ваша ссылка на огромный блок кода, вы не хотите копировать / вставлять тот же самый точный код среди всех других классов, которые могут быть предназначены для использования схожих общих «вещей», которые нужно сделать.

Для простого примера, у вас есть функциональность, которая формирует имя и адрес человека (или название и адрес компании). У вас есть код, который ожидает имя и до 3 строк адреса, а также город, штат, почтовый индекс (или любой другой). Таким образом, форматирование такой информации имя / адрес одинаково для человека и бизнеса. Вы не хотите копировать этот точный метод снова и снова между ними. Однако у каждого отдельного класса есть свои вещи, за которые он отвечает.

Я знаю, что это простой пример для контекста, но я думаю, что все понятно.

Проблема только с определением интерфейса заключается в том, что он не позволит вам фактически реализовать код, на который вы ссылаетесь.

Из вашего примера я бы подумал о том, чтобы сделать комбинацию вещей. Создайте статический класс с методами, которые вы можете использовать как «глобально» доступные. Разрешить передачу в него параметра экземпляра класса, который имеет тип интерфейса, который все остальные выразили, который будет гарантировать, что входящий объект имеет все «куски» свойств / методов, которые вы ожидаете, и заставит IT работать это по мере необходимости. Что-то вроде

public interface ITheyHaveInCommon
{
   string Name;
   string GetOtherValue();
   int SomethingElse;
}

public class Person : ITheyHaveInCommon
{
  // rest of your delcarations for the required contract elements
  // of the ITheyHaveInCommon interface...
}

public class Country : ITheyHaveInCommon
{
  // rest of your delcarations for the required contract elements
  // of the ITheyHaveInCommon interface...
}


public static class MyGlobalFunctions
{
   public static string CommonFunction1( ITheyHaveInCommon incomingParm )
   {
      // now, you can act on ANY type of control that uses the 
      // ITheyHaveInCommon interface...
      string Test = incomingParm.Name
                  + incomingParm.GetOtherValue()
                  + incomingParm.SomethingElse.ToString();

      // blah blah with whatever else is in your "huge" function

      return Test;
   }
}
0 голосов
/ 17 октября 2011

Вы можете реализовать шаблон стратегии:

class DisplayNameStrategy<T> {
    private readonly Func<T, string> nameSelector;
    public void DisplayNameStrategy(Func<T, string> nameSelector) {
        this.nameSelector = nameSelector;
    }

    public void abstract DisplayName(T t);
}

class WriteToConsoleDisplayNameStrategy<T> : DisplayNameStrategy<T> {
    public void WriteToConsoleDisplayNameStrategy(Func<T, string> nameSelector)
        : base(nameSelector) { }
    public override void DisplayName(T t) {
        Console.WriteLine(this.nameSelector(t));
}

public class Person {
    private readonly DisplayNameStrategy<Person> displayNameStrategy =
        new WriteToConsoleDisplayNameStrategy<Person>(x => x.Name);

    public string Name { get; set; }

    public void DisplayName() {
        this.displayNameStrategy(this);
    }
}

Примечание: возможно, лучше внедрить конкретную стратегию.

0 голосов
/ 17 октября 2011

Вы можете определить этот большой метод в отдельном классе, а затем вызвать метод в обоих вышеупомянутых классах. Для статического метода его можно вызвать с использованием синтаксиса classname.methodname().

Для нестатического метода вы должны будете сделать это:

classname obj=new classname();
obj.methodname();
0 голосов
/ 17 октября 2011

Вы можете создать либо метод статической утилиты DisplayName(), который вы передадите данные, необходимые для отображения, либо использовать композицию и переместить все свойства и соответствующие методы, такие как DisplayName(), в отдельный класс - затем использовать экземпляр этого классаот Country и Person.

0 голосов
/ 17 октября 2011

Пара опций:

  • Заставить оба класса реализовать интерфейс для общих членов (Name) и добавить метод расширения для поведения (или просто обычный статический метод)
  • Создание методов, которые используют экземпляр и лямбда-выражение для доступа к членам комментария, например,

    public static void Display<T>(T item, Func<T, string> nameGetter)
    

    Затем вы вызываете его с (скажем)

    DisplayHelper.Display(person, p => p.Name);
    

Интерфейсное решение более чистое, но использование делегата является более гибким - вам не нужно менять участвующие классы, и вы можете справиться с небольшими изменениями (например, PersonNameпротив FooName против Name)

...