1. 상속과 중복 코드

1. DRY(Don’t Repeat Yourself) 원칙

<aside> 💡

중복 코드는 변경을 방해한다

</aside>

2. 중복과 변경

1. 중복 코드 살펴보기

class Call
{
public:
	Call(const FLocalDateTime& InFrom, const FLocalDateTime& InTo)
		: From(InFrom)
		, To(InTo)
	{}

	FDuration GetDuration() const { return FDuration::Between(From, To); }
	FLocalDateTime GetFrom() const { return From; }

private:
	FLocalDateTime From;
	FLocalDateTime To;
};
class FPhone
{
public:
	FPhone(FMoney* InAmount, const FDuration& InSeconds)
		: Amount(InAmount)
		, Seconds(InSeconds)
	{}

	void Call(FCall* InCall) { Calls.push_back(InCall); }
	std::vector<FCall*> GetCalls() const { return Calls; }
	FMoney* GetAmount() const { return Amount; }
	FDuration GetSeconds() const { return Seconds; }

	FMoney* CalculateFee() const
	{
		FMoney* Result = const_cast<FMoney*>(FMoney::ZERO);
		for (FCall* Call : Calls)
		{
			Result = Result->Plus(Amount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
		}

		return Result;
	}

private:
	FMoney* Amount;
	FDuration Seconds;
	std::vector<FCall*> Calls;
};
FPhone* Phone = new FPhone(FMoney::Wons(5), FDuration::OfSeconds(10));
Phone->Call(new FCall(FLocalDateTime(2018, 1, 1, 12, 10, 0), FLocalDateTime(2018, 1, 1, 12, 11, 0)));
Phone->Call(new FCall(FLocalDateTime(2018, 1, 2, 12, 10, 0), FLocalDateTime(2018, 1, 2, 12, 11, 0)));

FMoney* Fee = Phone->CalculateFee();  // FMoney::Wons(60)
class FNightlyDiscountPhone
{
public:
	FNightlyDiscountPhone(FMoney* InNightlyAmount, FMoney* InRegularAmount, const FDuration& InSeconds)
		: NightlyAmount(InNightlyAmount)
		, RegularAmount(InRegularAmount)
		, Seconds(InSeconds)
	{
	}

	void Call(FCall* InCall) { Calls.push_back(InCall); }
	std::vector<FCall*> GetCalls() const { return Calls; }
	FMoney* GetAmount() const { return NightlyAmount; }
	FDuration GetSeconds() const { return Seconds; }

	FMoney* CalculateFee() const
	{
		FMoney* Result = const_cast<FMoney*>(FMoney::ZERO);
		for (FCall* Call : Calls)
		{
			if (Call->GetFrom().GetHour() >= LATE_NIGHT_HOUR)
			{
				Result = Result->Plus(NightlyAmount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
			}
			else
			{
				Result = Result->Plus(RegularAmount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
			}
		}

		return Result;
	}

private:
	constexpr int32_t LATE_NIGHT_HOUR = 22;

	FMoney* NightlyAmount;
	FMoney* RegularAmount;
	FDuration Seconds;
	std::vector<FCall*> Calls;
};

2. 중복 코드 수정하기

class FPhone
{
public:
	FPhone(FMoney* InAmount, const FDuration& InSeconds, double InTaxRate)
		: Amount(InAmount)
		, Seconds(InSeconds)
		, TaxRate(InTaxRate)
	{}

	void Call(FCall* InCall) { Calls.push_back(InCall); }
	std::vector<FCall*> GetCalls() const { return Calls; }
	FMoney* GetAmount() const { return Amount; }
	FDuration GetSeconds() const { return Seconds; }

	FMoney* CalculateFee() const
	{
		FMoney* Result = const_cast<FMoney*>(FMoney::ZERO);
		for (FCall* Call : Calls)
		{
			Result = Result->Plus(Amount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
		}

		return Result->Plus(Result->Times(TaxRate));
	}

private:
	FMoney* Amount;
	FDuration Seconds;
	std::vector<FCall*> Calls;

	double TaxRate;
};

class FNightlyDiscountPhone
{
public:
	FNightlyDiscountPhone(FMoney* InNightlyAmount, FMoney* InRegularAmount, const FDuration& InSeconds, double InTaxRate)
		: NightlyAmount(InNightlyAmount)
		, RegularAmount(InRegularAmount)
		, Seconds(InSeconds)
		, TaxRate(InTaxRate)
	{
	}

	void Call(FCall* InCall) { Calls.push_back(InCall); }
	std::vector<FCall*> GetCalls() const { return Calls; }
	FMoney* GetAmount() const { return NightlyAmount; }
	FDuration GetSeconds() const { return Seconds; }

	FMoney* CalculateFee() const
	{
		FMoney* Result = const_cast<FMoney*>(FMoney::ZERO);
		for (FCall* Call : Calls)
		{
			if (Call->GetFrom().GetHour() >= LATE_NIGHT_HOUR)
			{
				Result = Result->Plus(NightlyAmount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
			}
			else
			{
				Result = Result->Plus(RegularAmount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
			}
		}

		return Result->Plus(Result->Times(TaxRate));
	}

private:
	constexpr int32_t LATE_NIGHT_HOUR = 22;

	FMoney* NightlyAmount;
	FMoney* RegularAmount;
	FDuration Seconds;
	std::vector<FCall*> Calls;

	double TaxRate;
};

3. 타입 코드 사용하기

class FPhone
{
	using ThisClass = FPhone;

public:
	enum class EPhoneType
	{
		Regular,
		Nightly
	};

	FPhone(FMoney* InAmount, const FDuration& InSeconds, double InTaxRate)
		: ThisClass(EPhoneType::Regular, InAmount, const_cast<FMoney*>(FMoney::ZERO), const_cast<FMoney*>(FMoney::ZERO), InSeconds)
	{}

	FPhone(FMoney* InNightlyAmount, FMoney* InRegularAmount, const FDuration& InSeconds)
		: ThisClass(EPhoneType::Nightly, const_cast<FMoney*>(FMoney::ZERO), InNightlyAmount, InRegularAmount, InSeconds)

	{}

	FPhone(EPhoneType InType, FMoney* InAmount, FMoney* InNightlyAmount, FMoney* InRegularAmount, const FDuration& InSeconds)
		: Type(InType)
		, Amount(InAmount)
		, NightlyAmount(InNightlyAmount)
		, RegularAmount(InRegularAmount)
		, Seconds(InSeconds)
	{}

	void Call(FCall* InCall) { Calls.push_back(InCall); }
	std::vector<FCall*> GetCalls() const { return Calls; }
	FMoney* GetAmount() const { return Amount; }
	FDuration GetSeconds() const { return Seconds; }

	FMoney* CalculateFee() const
	{
		FMoney* Result = const_cast<FMoney*>(FMoney::ZERO);
		for (FCall* Call : Calls)
		{
			if (Type == EPhoneType::Regular)
			{
				Result = Result->Plus(Amount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
			}
			else
			{
				if (Call->GetFrom().GetHour() >= LATE_NIGHT_HOUR)
				{
					Result = Result->Plus(NightlyAmount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
				}
				else
				{
					Result = Result->Plus(RegularAmount->Times(Call->GetDuration().ToSeconds() / Seconds.ToSeconds()));
				}
			}
		}

		return Result;
	}

private:
	constexpr int32_t LATE_NIGHT_HOUR = 22;

	EPhoneType Type;
	FMoney* Amount;
	FMoney* NightlyAmount;
	FMoney* RegularAmount;
	FDuration Seconds;
	std::vector<FCall*> Calls;
};

2. 상속을 이용해서 중복 코드 제거하기