Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Повторное использование кода и проблема множественного наследования в C#

#c_sharp


Существует следующая структура приложения:



Реализация метода Id из интерфейса IGetId одинаковая везде. Но, исходя из этой структуры
его нужно будет каждый раз переписывать в каждом классе, имплементирующего интерфейсы,
порожденные от IGetId. 

Если бы не было BaseModel это в принципе не было бы проблемой, т.к. можно было бы
вынести реализацию этого метода в какой нибудь абстрактный класс AGetId и от него наследоваться,
но множественное наследование не поддерживается, а композиция в данном случае нарушает
целостность.

В PHP существует очень удобный инструмент - Trait. Он отлично решает данную проблему,
но, к сожалению, в C# такого функционала нету.

В C# есть нечто похожее - extension methods, хоть это и совершенно разные вещи. Я
пробовал сделать что-то с использованием методов расширения, но получилось на мой взгляд
"не очень":

IGetId:

public interface IGetId
{
    int Id { get; }
}


Extension methods:

public static class CoreExtensions
{       
    public static int Id(this IGetId obj)
    {
        return obj.Id;
    }
}


В любом случае, это не дало ожидаемого результата, мне по прежнем нужно реализовывать
Id в каждом классе. 

Существуют ли какие-то средства для решения данной проблемы?
    


Ответы

Ответ 1



Я бы предложил такую иерархию наследования: // библиотечные интерфейсы interface IGetId { int Id { get; } } interface IPerson : IGetId { string Name { get; } } interface IAddres : IGetId { string City { get; } } // универсальный базовый класс class BaseModel { /* ... */ } // вспомогательный класс abstract class BaseModelWithId : BaseModel, IGetId { int id; public int Id => id; } // реальные классы class Person : BaseModelWithId, IPerson { public string Name { get; set; } } Необходимость реализовывать IGetId в Person отпала. Хотя в общем случае вы правы, trait'ов не хватает. Есть хорошие шансы, что они появятся в будущем C# 8.

Ответ 2



Замените наследование композицией: class Id { public int Value { get; set; } public Id(int value) => Value = value; public override bool Equals(object obj) => ((obj is Id id) && Value.Equals(id.Value)) || ((obj is int i) && Value.Equals(i)); public override int GetHashCode() => Value.GetHashCode(); public static implicit operator int(Id id) => id.Value; public static implicit operator Id(int i) => new Id(i); } Ну, действительно, классы же ваши включают в себя Id. Используем: interface IPerson { string Name { get; } } class Person : IPerson { public Id Id { get; set; } public string Name { get; set; } } Вместо интерфейса IGetId теперь вы везде используете класс Id и его свойство Value.

Ответ 3



Для соблюдения плюрализма мнений, добавлю вариант с идентификатором в базовом классе: interface IGetId { int Id { get; } } interface IPerson : IGetId { string Name { get; } } interface IAddres : IGetId { string City { get; } } Базовый класс: class BaseModel : IGetId { public virtual int Id { get { /* ... */ } } } Конкретный class PersonModel : BaseModel, IPerson { public string Name { get; set; } } (Я бы переименовал интерфейс IGetId во что-то на подобии IМодельСИдентификатором и, как следствие базовую модель)

Ответ 4



С помощью методов расширения действительно можно решить вашу проблему. Делается это следующим образом. Из интерфейса убираем свойство Id. Остаётся просто: public interface IGetId { } В методе расширения пишем код получения Id: public static class CoreExtensions { public static int Id(this IGetId obj) { return 42; } } Можно использовать: var person = new Person(); Console.WriteLine(person.Id()); // вызывается extension method IGetId g = person; Console.WriteLine(g.Id()); // аналогично

Комментариев нет:

Отправить комментарий