Loading...
Wanker avatar Wanker 15 Точки

Валидация в конструктор?

Въпрос към Жоро или който може да даде съвет. :)

Имаме примерен клас:

class Person
{
private:
	std::string name;
	int age;
	
public:
	Person(std::string name, int age)
		: name(name), age(age)
	{
	}

	std::string GetName() const
	{
		return this->name;
	}

	void SetName(std::string name)
	{
		if (name.empty())
		{
			throw "Person name cannot be empty.";
		}

		this->name = name;
	}

	int GetAge() const
	{
		return this->age;
	}

	void SetAge(int age)
	{
		if (age <= 0)
		{
			throw "Invalid person age.";
		}

		this->age = age;
	}
}

Имаме дадена валидация в сетърите, но в конструктора чрез инициализиращия лист няма как да направим тази валидация.

От C# знаем, че се минава през пропъртитата в конструктора, дали това може да се приложи и тук? Например:

Person(std::string name, int age)
{
	this->SetName(name);
	this->SetAge(age);
}

Но така пък не се използва инициализиращия лист...

Въобще как е добрата практика в такъв случай?

Благодаря.

0
C++ Programming
georgi.stef.georgiev avatar georgi.stef.georgiev 921 Точки
Best Answer

Здравей,

Хубав въпрос, заслужава си малко по-голямо обяснение. Ще разделя отговора на две части:

1. АКО ползваме initializer list на конструктора (защото ни се налага заради членове, базов клас или нещо друго, или защото такъв стил сме избрали:

- Изнасяме си валидиращата логика в отделни функции. Това така или иначе е добра практика, защото изчистваме setter-ите от валидационния код, което ги прави по-лесни за четене (общо взето е очевидно какво правят)

void setAge(int age) {
    this->age = verifyAge(age);
}

// тук спокойно може параметърът и return типа да са const int&, но за int променливи дали ще предаваш по референция или по копие общо взето е еднакво бързо
int verifyAge(int age) {
    if(age < 0) {
        throw "Invalid age";
    }

    return age;
}

- Обърни внимание, че verifyAge връща int. Това на практика за setter-а не ни е нужно (можеше просто на 1 ред да verify-нем, а на следващия да присвоим). Обаче понеже искаме да го ползваме в initializer list-а, се принуждаваме да имаме return на стойността, ако е валидна:

Person(int age) :
    age(verifyAge(age)) {
}

- Така първо се изпълнява валидацията, която е във verifyAge, и ако тя мине, тогава получаваме стойност в age полето, която сме сигурни, че е вярна

- Между другото, в конкретния случай бих направил verifyAge да е static метод, защото не е свързан с текущия обект (не ползва this по никакъв начин), а е проверка която е еднаква за всички обекти от този клас. Но, може да имаш валидационна логика, която Е свързана с текущото състояние на обекта, в които случаи не може да е static. Също така verify може би трябва да е validate, ама вече съм тръгнал така да го пиша :D...

- Обърни внимание, че тези валидации има смисъл да се правят САМО за членове, които НЕ СА обекти на КЛАСОВЕ. Това е защото всеки член, който е обект от клас би следвало да си има собствен конструктор, който прави валидация сам за себе си. 

2. Може и да не ползваме initializer list изобщо, не е лош стил, на много места ще срещнеш код, който не ползва. Най-общо казано, ако инициализираш членове, които са примитивни типове данни е едно и също дали ще го направиш с initializer list или като извикаш setter в тялото на конструктора.

- Както казахме и преди малко, валидации има смисъл да правиш само когато съответния член е примитивен тип, така че съвсем спокойно можеш примитивните членове да ги инициализираш със setters в тялото на конструктора, а тези, които са обекти, да ги инициализираш в initializer list-а

- Въпреки това на много места кодът за инициализацията не ползва initializer list, независимо, че членовете са обекти - това най-често се случва под формата на init() методи. Тези init() методи най-често се пишат за да може няколко различни конструктора да преизползват една и съща логика (до преди C++11 не можеше от един конструктор да викаш друг и има много legacy код, който е писан като за C++03...) и се викат от тялото на конструктора. Съответно init() методите също могат да си викат каквито setters си искат

Така де, най-близкото нещо до правило, което бих ти дал, е: членовете, които са класове - в initializer list-а и без валидация, защото техните конструктори трябва да ги валидират, а членовете, които са просто примитивни типове (int, float...) - в тялото на конструктора през setter методи, които валидират. Това поне бих направил аз, но - отново - в C++ няма "правилни" и "грешни" начини, така че не го приемай за нещо, което не търпи промяна, в определени ситуации може да е по-подходящ друг подход. Важното е да разбираш кое какво прави, а как го правиш винаги ще е въпрос на стил.

Поздрави,

Жоро

1
01/04/2017 01:19:50
Wanker avatar Wanker 15 Точки

Много изчерпателен отговор. Не остави място за допълнителни въпроси. :)

Мерси, Жоро!

0
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.