Как организовать классы C #, которые наследуют друг от друга, но также имеют свойства, которые наследуют друг от друга? - PullRequest
8 голосов
/ 27 марта 2012

У меня есть приложение, которое имеет концепцию Venue, место, где происходят события. A Venue имеет много VenuePart s. Итак, это выглядит так:

public abstract class Venue
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<VenuePart> VenueParts { get; set; }
}

A Venue может быть GolfCourseVenue, то есть Venue, имеющим наклон и определенный тип VenuePart, называемый HoleVenuePart:

public class GolfCourseVenue : Venue
{
    public string Slope { get; set; }
    public virtual ICollection<HoleVenuePart> Holes { get; set; }
}

В будущем могут быть и другие виды Venue s, которые все наследуются от Venue. Они могут добавлять свои собственные поля и всегда будут иметь VenuePart своего собственного специфического типа.

Вот классы VenuePart:

public abstract class VenuePart
{
    public int Id { get; set; }
    public string Name { get; set; }
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public override string NameDescriptor { get { return "Hole"; } }
    public int Yardage { get; set; }
}

Мои объявления выше кажутся неправильными, потому что теперь у меня есть GolfCourseVenue с двумя коллекциями, тогда как на самом деле он должен иметь только одну. Я не могу переопределить это, потому что тип отличается, верно? Когда я запускаю отчеты, я хотел бы ссылаться на классы в общем, где я просто выплевывал Venue с и VenuePart с. Но когда я рисую формы и тому подобное, я бы хотел быть конкретным.

У меня много таких отношений, и мне интересно, что я делаю неправильно. Например, у меня есть Order, который имеет OrderItem с, но также есть определенные виды Order с, которые имеют определенные виды OrderItem с.

Обновление: Следует отметить, что эти классы являются сущностями Entity Framework Code-First. Я надеялся, что это не будет иметь значения, но я думаю, что это может. Мне нужно структурировать классы таким образом, чтобы Code-First мог правильно создавать таблицы. Не похоже, что Code-First может обрабатывать дженерики. Извините, эта деталь реализации мешает элегантному решению: /

Обновление 2: Кто-то связался с поиском, который указывал на Ковариантность и Контравариантность , который, по-видимому, был способом ограничения списков внутри подтипов самими для данного подтипа. Это кажется действительно многообещающим, но человек удалил свой ответ! У кого-нибудь есть информация о том, как я могу использовать эти понятия?

Обновление 3: Удалены свойства навигации, которые были в дочерних объектах, потому что это сбивало с толку людей и не помогало описать проблему.

Ответы [ 4 ]

2 голосов
/ 27 марта 2012

Вот один из возможных вариантов с использованием обобщений:

public abstract class VenuePart
{
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public string NameDescriptor { get{return "I'm a hole venue"; } }
}

public class Venue<T> where T : VenuePart
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Company Company { get; set; }
    public virtual ICollection<T> VenueParts { get; set; }
}

public class GolfCourseVenue : Venue<HoleVenuePart>
{
}

Здесь у GolfCourseVenue есть коллекция VenueParts, которая может содержать HoleVenueParts или суперклассы HoleVenueParts.Другие специализации Venue будут ограничивать VenueParts тем, что они содержат VenueParts, специфичные для этого места.

Вторая возможность почти такая же, как у вас

public abstract class VenuePart
{
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public string NameDescriptor { get{return "I'm a hole venue"; } }
}

public class Venue 
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Company Company { get; set; }
    public virtual ICollection<VenuePart> VenueParts { get; set; }
}

public class GolfCourseVenue : Venue
{
}

Теперь у GolfCourseVenue есть коллекция VenueParts, которая может содержатьVenueParts или супер классы VenueParts.Здесь все специализации Venue могут содержать любой тип VenuePart, который может или не может быть уместным.

В ответ на ваш комментарий о ковариации я бы предложил что-то вроде этого:

public abstract class VenuePart
{
    public abstract string NameDescriptor { get; }
}

public class HoleVenuePart : VenuePart
{
    public override string NameDescriptor { get{return "I'm a hole venue"; } }
}

public abstract class Venue 
{
    public int Id { get; set; }
    public string Name { get; set; }
    public abstract ICollection<VenuePart> VenueParts { get; }
}

public class GolfCourseVenue : Venue
{
    private ICollection<HoleVenuePart> _holeVenueParts;

    public GolfCourseVenue(ICollection<HoleVenuePart> parts)
    {
       _holeVenueParts = parts;
    }

    public override ICollection<VenuePart> VenueParts 
    { 
        get
        { 
            // Here we need to prevent clients adding
            // new VenuePart to the VenueParts collection. 
            // They have to use Add(HoleVenuePart part).
            // Unfortunately only interfaces are covariant not types.
            return new ReadOnlyCollection<VenuePart>(
                   _holeVenueParts.OfType<VenuePart>().ToList()); 
        } 
    }

    public void Add(HoleVenuePart part) { _holeVenueParts.Add(part); }
}
1 голос
/ 27 марта 2012

Я с нетерпением жду совета от других - но мой подход заключается в использовании дженериков в этом случае. При использовании дженериков ваши "части" GolfCourseVenue строго напечатаны!

... и когда я это печатаю, все остальные тоже говорят дженерики. КАК ВЫ набираете так быстро, как оверсакеры?!

В любом случае, делая вид, что я все еще первый -

public class VenuePart
{
}

public class HoleVenuePart : VenuePart
{
}

public abstract class Venue<T> where T : VenuePart
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual Company Company { get; set; }
  public virtual ICollection<T> Parts { get; set; }
}

public class GolfCourseVenue : Venue<HoleVenuePart>
{
  public string Slope { get; set; }
}

Кроме того, как второй вариант, вы также можете использовать интерфейс, поэтому, если вам не нравится имя Parts, вы можете назвать его Holes, когда известно, что производный тип - GolfCourse

public class VenuePart
{
}

public class HoleVenuePart : VenuePart
{
}

public interface IPartCollection<T> where T : VenuePart
{
  ICollection<T> Parts { get; set; }
}

public abstract class Venue<T> : IPartCollection<T> where T : VenuePart
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual Company Company { get; set; }
  public virtual ICollection<T> Parts { get; set; }
}

public class GolfCourseVenue : Venue<HoleVenuePart>
{
  public string Slope { get; set; }
  ICollection<HoleVenuePart> IPartCollection<HoleVenuePart>.Parts { get { return base.Parts; } set { base.Parts = value; }}

  public virtual ICollection<HoleVenuePart> Holes { get { return base.Parts; } set { base.Parts = value;}}
}
1 голос
/ 27 марта 2012

Вы можете использовать Ковариация

public abstract class Venue
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual Company Company { get; set; }
    public virtual IEnumerable<VenuePart> VenueParts { get; set; }
}

public class GolfCourseVenue : Venue
{
    public string Slope { get; set; }

    public  GolfCourseVenue()
    {
        List<HoleVenuePart> HoleVenueParts = new List<HoleVenuePart>();
        HoleVenueParts.Add(new HoleVenuePart());
        VenueParts = HoleVenueParts;
    }
}

Предполагая, что HoleVenuePart наследуется от VenuePart

0 голосов
/ 27 марта 2012

Если вы удалите «заданные» части обеих коллекций, это будет более целесообразно: базовый класс предоставляет коллекцию «все части», а производные классы имеют фильтрованное представление в дополнение к базовому классу один.

Примечание. В зависимости от ваших потребностей, если GolfVenue будет иметь специализацию общего типа Venue<VenuePart>, она может не работать, так как Venue<Type1> и Venue<Type2> не будет иметь хорошего базового класса для работы.

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

public interface IVenue 
{ 
    public int Id { get; } 
    public string Name { get; } 
    public virtual IEnumerabe<VenuePart> VenueParts { get; } 
} 

public interface IGolfCourse : IVenue
{ 
    public virtual IEnumerabe<HoleVenuePart> Holes { get; } 
} 

Теперь вы можете использовать GolfCourse: Venue из других образцов, но, поскольку он реализует интерфейс, вы можете обрабатывать его и в общих чертах:

class GolfCourse:Venue<HoleVenuePart>, IGolfCourse  {
  public virtual IEnumerabe<VenuePart> Holes{ get 
    { 
      return VenueParts.OfType<HoleVenuePart>(); 
    }
  } 
}
class OtherPlace:Venue<VenuePart>, IVenue {...}

List<IVenue> = new List<IVenue> { new GolfCourse(), new OtherPlace() };

Не то, что GolfCourse и OtherPlace не имеет общего родительского класса (кроме объекта), поэтому без интерфейса вы не сможете использовать их взаимозаменяемо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...