Разрабатываю библиотеку по работе со схемой, где доменная логика следующая:
Если разработчик, который будет использовать мою либу попытаеться обратиться к таблице (Scheme.Table.Rows
), а у схемы ее нет, то вместо NullReferenceException
, я хочу, чтобы выбрасывалась более внятная ошибка. И для было решено реализовать паттерн Null Object
.
Сейчас это сделано через интерфейс и вот как это выглядит в проекте:
public class Scheme
{
public ITable Table { get; }
public IPicture Picture { get; }
/// <summary>
/// Creates a new <see cref="Scheme"/> instance with table content.
/// </summary>
public Scheme(ITable table)
{
Table = table;
Picture = new NoPicture();
}
/// <summary>
/// Creates a new <see cref="Scheme"/> instance with picture content.
/// </summary>
public Scheme(IPicture picture)
{
Table = new NoTable();
Picture = picture;
}
}
public interface ITable
{
IEnumerable<Row> Rows { get; }
}
public class Table : ITable
{
public IEnumerable<Row> Rows { get; }
}
public class NoTable : ITable
{
public IEnumerable<Row> Rows => throw new Exception("Scheme does not contain a table.");
}
public interface IPicture
{
Image Image { get; }
}
public class Picture : IPicture
{
public Image Image { get; }
}
public class NoPicture : IPicture
{
public Image Image => throw new Exception("Scheme does not contain a picture.");
}
А вот как бы это выглядело на абстрактных классах:
public class Scheme
{
public AbstractTable Table { get; }
public AbstractPicture Picture { get; }
/// <summary>
/// Creates a new <see cref="Scheme"/> instance with table content.
/// </summary>
public Scheme(AbstractTable table)
{
Table = table;
Picture = new NoPicture();
}
/// <summary>
/// Creates a new <see cref="Scheme"/> instance with picture content.
/// </summary>
public Scheme(AbstractPicture picture)
{
Table = new NoTable();
Picture = picture;
}
}
public abstract class AbstractTable
{
public abstract IEnumerable<Row> Rows { get; }
}
public class Table : AbstractTable
{
public override IEnumerable<Row> Rows { get; }
}
public class NoTable : AbstractTable
{
public override IEnumerable<Row> Rows => throw new Exception("Scheme does not contain a table.");
}
public abstract class AbstractPicture
{
public abstract Image Image { get; }
}
public class Picture : AbstractPicture
{
public override Image Image { get; }
}
public class NoPicture : AbstractPicture
{
public override Image Image => throw new Exception("Scheme does not contain a picture.");
}
Это очередной случай, когда одно и тоже можно реализовать с помощью интерфейса или абcтрактного класса. Но с точки зрения логики и дизайна, что правильнее?
Я обычно использую абстрактный класс, чтобы вынести общий код в отдельное место и самое главное, чтобы отношение между сущностями было типа IS, но что-то затрудняюсь сказать, что NoTabel
is AbstractTable
.
Для решения этой задачи
Если разработчик, который будет использовать мою либу попытаеться обратиться к таблице (Scheme.Table.Rows), а у схемы ее нет, то вместо NullReferenceException, я хочу, чтобы выбрасывалась более внятная ошибка.
не нужно использовать паттерн Null Object
. Достаточно бросать исключения в свойствах:
public class Scheme
{
private readonly Table table;
private readonly Picture picture;
public Table Table => table ?? throw new Exception("Scheme does not contain a table.");
public Picture Picture => picture ?? throw new Exception("Scheme does not contain a picture.");
// со слов автора, такое свойство имеется
public bool HasTable => table != null;
public Scheme(Table table)
{
this.table = table ?? throw new ArgumentNullException(nameof(table));
}
public Scheme(Picture picture)
{
this.picture = picture ?? throw new ArgumentNullException(nameof(picture));
}
}
public class Table
{
public override IEnumerable<Row> Rows { get; }
}
public class Picture
{
public override Image Image { get; }
}
Если нужна абстракция зависимостей от Table
и Picture
, добавляем интерфейсы:
public class Scheme
{
...
public Scheme(ITable table) { ... }
public Scheme(IPicture picture) { ... }
}
public interface ITable { ... }
public class Table : ITable { ... }
public interface IPicture { ... }
public class Picture : IPicture { ... }
Есть несколько реализаций интерфейсов с общим кодом? Добавляем абстрактный класс:
public abstract class AbstractTable : ITable { ... }
public class Table : AbstractTable { ... }
public class AbstractPicture : IPicture { ... }
public class Picture : AbstractPicture { ... }
Я думаю что интерфейсами будет правильнее и вот почему.
Интерфейсы изначально придумывались как промежуточный объект между двумя функциональными объектами.
Абстрактные классы придумывались, чтобы уменьшить код с помощью выноса общих свойств и методов.
В вашем случае, если вы планируете использовать в других проектах, вам проще будет ориентироваться по интерфейсам. И ссылаться проще, и комментарии к интерфейсам будет проще читать.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Есть два разных канваса, и нужно чтобы объект на одном был точно поверх объекта на другом
Я вот попытался сделать проект на Code First, позволяющий пользователю загружать картинки в базу данныхСоздал классы моделей и класс контекста...
По причине политики информационной безопасности на предприятии, нет возможности напрямую подключиться к сервисам google на сервере с бекэндом...