Class Constructor и Get Design

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

В пример, ако имам клас кола

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
Защо една кола трябва да знае как да се измъкне от базата данни? Нито едно от вашите предложения не е добро и е нарушение на СРП.   -  person Philip Stuyck    schedule 09.07.2015


Отговори (4)


Бих препоръчал да отделите достъпа до вашата база данни от класовете на вашия модел на домейн като Car.

Това може да стане чрез поставяне на всички операции за извличане на достъп до базата данни в техен собствен набор от класове. Те се наричат ​​хранилища. Има различни подходи за създаване на тези хранилища. Зависи от това как вашите данни се съхраняват в базата данни и дали използвате рамка на обекта. Някои предпочитат общи хранилища, докато други изобщо не харесват този общ подход. Но най-важното е, че хранилищата се грижат за достъпа до базата данни за извличане на обекти и за съхранение. Това не трябва да се извършва от обекта на домейна, т.е. самия Car, защото той трябва да има само операции, които принадлежат на Car и това, което го прави обект 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
В момента, в който Car repository извлече данните, които принадлежат на автомобила, той трябва да извлече всички аспекти за автомобила, които са важни и това, което прави колата кола. Максималната скорост очевидно е атрибут на колата, така че да, тя трябва да бъде извлечена директно, когато заредите колата от Db. Можете да отидете още по-далеч в тази посока, като направите дизайн, управляван от домейн, и използвате само обобщен корен за входните точки. Но това е друга тема. methodsandtools.com/archive/archive.php?id=97p2 - person Philip Stuyck; 09.07.2015

Както казах в коментара си, бих направил това, за да управлявам колите и да позволя зареждането на кола по id или нещо друго, с което искате да я заредите.

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
Харесвам този пример, но внедряването изглежда изисква много режийни разходи. Например, за да получа кола по Id, първо трябва да инстанцирам обект 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
Това не предлага лесен начин за зареждане на кола по id. Това просто създава нова кола с идентификатор. - person deathismyfriend; 09.07.2015
comment
Кодът за зареждане не е много важен, така че не сложих много страничен DB код. Важното за примера е, че той подчертава как да запазите кода, който създава обекта, извън самия код на класа на обекта. Оттук и коментарите след извикването на конструктора. - person BardMorgan; 09.07.2015
comment
Не, коментарът ясно посочва, че можете да заредите колата от базата данни. Добавете начин за съхранение към тази така наречена „фабрика“ и това, което имате, е хранилище. - person Philip Stuyck; 09.07.2015