Конструктор классов и получение дизайна

При разработке класса и конструкторов у меня возникает вопрос относительно получения записи.

В примере, если у меня есть класс автомобиля

public Car
{
    ...
}

У меня есть возможность создать конструктор, который мог бы получить, например, определенный экземпляр автомобиля:

public Car(int carID)
{
    //Get car from database
}

Или я мог бы написать общедоступный метод для получения машины:

public Car GetCarByID(int carID)
{
    //Get car from database
}

Существует ли предпочтительный (лучший) метод получения экземпляра класса? Код, кажется, читается чище, чтобы получить экземпляр объекта:

Car MyCar = new Car(5);

vs.

Car MyCar = new Car();
MyCar.GetCarByID(5);

person TreK    schedule 09.07.2015    source источник
comment
В этом случае у меня был бы класс car, который обрабатывает всю основную информацию. (как идентификатор). Затем у меня был бы класс CarCollection, который содержит автомобили в коллекции. В этом классе у меня будет метод GetCarById. В этом методе я просматривал коллекцию автомобилей и возвращал автомобиль с этим идентификатором. Этот вопрос очень основан на мнении, хотя здесь он не по теме.   -  person deathismyfriend    schedule 09.07.2015
comment
Разделение проблем, если ваш класс car действительно является классом, который содержит информацию о car, вы должны использовать уровень доступа к данным для создания объектов car, не требуя, чтобы car знал о базе данных.   -  person Ron Beyer    schedule 09.07.2015
comment
Зачем машине знать, как получить себя из базы данных? Ни одно из ваших предложений не является хорошим и нарушает SRP.   -  person Philip Stuyck    schedule 09.07.2015


Ответы (4)


Я бы рекомендовал отделить ваш доступ к базе данных от ваших классов модели предметной области, таких как Car.

Это можно сделать, поместив все операции доступа к базе данных в свой собственный набор классов. Они называются репозиториями. Существуют разные подходы к созданию этих хранилищ. Это зависит от того, как ваши данные хранятся в базе данных и используете ли вы структуру сущностей. Некоторые предпочитают общие репозитории, тогда как другим такой общий подход вообще не нравится. Но суть в том, что репозитории заботятся о доступе к базе данных для извлечения объектов и их хранения. Это не должен делать объект домена, т.е. сам автомобиль, потому что он должен иметь только операции, которые принадлежат автомобилю и делают его объектом автомобиля. Хранение автомобиля в базе данных — это не то, что автомобиль должен уметь делать.

Пример использования шаблона репозитория можно найти здесь: http://www.codeproject.com/Articles/688929/Repository-Pattern-and-Unit-of

В MSDN также есть хорошая статья о шаблоне репозитория: https://msdn.microsoft.com/en-us/library/ff649690.aspx?f=255&MSPPError=-2147217396

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

Гораздо лучше, чем создавать методы в ваших классах, которые им не принадлежат.

person Philip Stuyck    schedule 09.07.2015
comment
Спасибо за объяснение. Хотя это поднимает другой вопрос, если бы у меня был метод DriveCar(), который должен был получить доступ к максимальной скорости автомобилей из базы данных, я думаю, что метод DriveCar() принадлежал бы к определению класса, вызывал бы он затем класс репозитория автомобилей для получить максимальную скорость? Или я неправильно об этом думаю? - person TreK; 09.07.2015
comment
В тот момент, когда репозиторий автомобилей извлекает данные, относящиеся к машине, он должен извлечь все важные аспекты автомобиля и то, что делает автомобиль автомобилем. Максимальная скорость явно является атрибутом автомобиля, поэтому да, ее следует получать непосредственно при загрузке автомобиля из базы данных. Вы можете пойти еще дальше в этом направлении, выполнив проектирование на основе предметной области и используя только совокупный корень для точек входа. Но это другая тема. methodsandtools.com/archive/archive.php?id=97p2 - person Philip Stuyck; 09.07.2015

Как я уже сказал в своем комментарии, я бы сделал это, чтобы управлять автомобилями и разрешить загрузку автомобиля по идентификатору или чему-то еще, что вы хотите загрузить.

public class Car
{
    public int Id { get; set; }
    public Car() { }
    public Car(int id) { Id = id; }
}

public class CarCollection
{
    public List<Car> Cars { get; set; }

    public void AddCar(Car car) { Cars.Add(car); }

    public Car GetById(int id) { return Cars.Single(x => x.Id == id); }

    public CarCollection(List<Car> cars) { Cars = cars; }
    public CarCollection(Car car) { Cars = new List<Car>() { car }; }
}
person deathismyfriend    schedule 09.07.2015
comment
Мне нравится этот пример, однако реализация, похоже, требует больших накладных расходов. Например, чтобы получить автомобиль по идентификатору, мне сначала нужно создать экземпляр объекта Car, а затем создать экземпляр объекта CarCollection для заполнения объекта Car? В некоторых других примерах используются статические классы, но, судя по моему чтению, статические классы не должны использоваться таким образом? - person TreK; 09.07.2015
comment
Я не понимаю, как это требует больших накладных расходов, поскольку вам все равно нужно будет хранить все автомобили в списке/массиве/словаре/другой структуре данных. Коллекция позволяет легко получить доступ к нужным автомобилям без написания статических методов. В этих статических методах вам нужно будет передать им коллекцию автомобилей, чтобы они могли перебирать автомобили и выбирать нужные. Класс выше уже справляется с этим. В качестве примера, если вы когда-либо использовали DataGridView, вы также заметите, что это похоже на классы RowCollection и ColumnCollection. - person deathismyfriend; 10.07.2015

Одним из простых решений является использование статического метода, например:

public static Car GetById(int carId)
{
    ... Load car and return it ...
}

Затем вы можете назвать это так:

Car myCar = Car.GetById(18); 
person zmbq    schedule 09.07.2015
comment
Автомобиль определенно не следует расширять такими методами. Это нарушает SRP, и его будет сложно протестировать. - person Philip Stuyck; 09.07.2015
comment
О, это действительно зависит. Для небольшого проекта добавление такого статического метода может сэкономить вам время, ничего вам не стоив. Вот почему я сказал, что это одно из решений, и притом простое. - person zmbq; 09.07.2015

Чтобы ответить на комментарии о разделении интересов, я бы предложил следующее:

public class Car
{
    public string Make {get; set;}
    public string Model {get; set;}
    // additional properties here.
}

public static class CarFactory
{
    public static Car CreateCar(string modelId)
    {
        Car car = new Car();
        // Load the car via whatever mechanism you wish;
        // e.g., web service, database, etc.
        return car;
}

Car fordFairlane = CarFactory.CreateCar("57fordFairlane");

Это сохраняет код для создания объекта автомобиля отдельно от самого объекта автомобиля и позволяет вам изменить способ создания автомобиля, даже не касаясь класса автомобиля.

person BardMorgan    schedule 09.07.2015
comment
Это не предлагает простой способ загрузить машину по идентификатору. Это просто создает новую машину с идентификатором. - person deathismyfriend; 09.07.2015
comment
Код загрузки на самом деле не важен, поэтому я не добавлял много лишнего кода БД. В этом примере важно то, что он подчеркивает, как сохранить код, создающий объект, вне кода самого класса объекта. Отсюда и комментарии после вызова конструктора. - person BardMorgan; 09.07.2015
comment
Нет, в комментарии ясно сказано, что вы можете загрузить машину из базы данных. Добавьте способ хранения к этой так называемой «фабрике», и вы получите репозиторий. - person Philip Stuyck; 09.07.2015