코드 재활용과 생산라인 증설 - 轉
이철우
객체지향의 '물려받기’를 이용하여 코드를 재활용하며 생산라인을 증설하자. 다양한 방법이 있겠지만 ‘기존 클래스 Factory를 물려받기’, ‘추상 클래스 만들어 물려받기’, ‘의존성 주입 이용하기’, 이렇게 세 가지 방법을 소개한다.
기존 클래스 Factory를 물려받기
이 방법에서 Factory를 '물려주기’위해 Factory.cs를 조금 고치자. private 필드들은 proteted로, 증설라인의 기능에 맞게 재작성할 Run(), Stop()은 virtual로 바꾸어야 한다. 그리고 새 재료인 기름과 새 생산품목을 필드로 더하고 증설 생산라인의 기능을 작성한다. 이 클래스를 FactoryInherited.cs에 담자.
- 물려주기 위해 고친 Factory.cs -
internal class Factory : IFactory
{
protected string? _input;
protected bool _isInputCorrect;
protected int _wood, _steel;
protected int _output;
public void GetReady()
{
_isInputCorrect = false;
_input = Console.ReadLine();
}
public virtual void Run()
{
var words = _input!.Split(' ');
if (words!.Length == 2)
{
_isInputCorrect = true;
_wood = int.Parse(words[0]);
_steel = int.Parse(words[1]);
_output = _wood + _steel;
}
else
{
_isInputCorrect = false;
_output = 0;
}
}
public virtual void Stop()
{
if (_isInputCorrect)
{
Console.WriteLine($"{nameof(Factory)} Output: {_output}");
}
else
{
Console.WriteLine($"{nameof(Factory)} Input is not correct.");
}
}
}
- 물려받아 작성한 증설라인 FactoryInherited.cs -
internal class FactoryInherited : Factory
{
private int _oil;
private int _output2;
public override void Run()
{
var words = _input!.Split(' ');
if (words.Length == 3)
{
_isInputCorrect = true;
_wood = int.Parse(words[0]);
_steel = int.Parse(words[1]);
_oil = int.Parse(words[2]);
_output = _wood + _steel;
_output2 = _steel + _oil;
}
else
{
_isInputCorrect = false;
_output = 0;
_output2 = 0;
}
}
public override void Stop()
{
if (_isInputCorrect)
{
Console.WriteLine($"{nameof(FactoryInherited)} Output1: {_output} Output2: {_output2}");
}
else
{
Console.WriteLine($"{nameof(FactoryInherited)} Input is not correct.");
}
}
}
두 생산라인을 가동하기 위해 ProgramByName.cs를 바꿔 ProgramInherited.cs에 담자.
- ProgramInherited.cs -
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
var lines = new List<IFactory>
{
new Factory(),
new FactoryInherited(),
};
foreach (var line in lines)
{
line.GetReady();
line.Run();
line.Stop();
}
실행하니 작동한다. IFactory ← Factory ← FactoryInherited 이렇게 물려받았다. 클래스 Factory의 필드 몇 개와 메서드 GetReady()를 재활용 했다.
추상 클래스 만들어 물려받기
조금 더 용감(?)하게 고도화(!)하자. 클래스 FactoryInherited에서 재활용한 필드, 메서드를 추상 클래스 FactoryAbstract에 담고, 이를 물려받아 기존 생산라인(FactoryLegacy.cs)과 증설 생산라인(FactoryAdded.cs)을 구현하자.
- FactoryAbstract.cs -
internal abstract class FactoryAbstract : IFactory
{
protected string? _input;
protected bool _isInputCorrect;
protected int _wood, _steel;
protected int _output;
public void GetReady()
{
_isInputCorrect = false;
_input = Console.ReadLine();
}
public abstract void Run();
public abstract void Stop();
}
- FactoryLegacy.cs -
internal class FactoryLegacy : FactoryAbstract
{
public override void Run()
{
var words = _input!.Split(' ');
if (words!.Length == 2)
{
_isInputCorrect = true;
_wood = int.Parse(words[0]);
_steel = int.Parse(words[1]);
_output = _wood + _steel;
}
else
{
_isInputCorrect = false;
_output = 0;
}
}
public override void Stop()
{
if (_isInputCorrect)
{
Console.WriteLine($"{nameof(FactoryLegacy)} Output: {_output}");
}
else
{
Console.WriteLine($"{nameof(FactoryLegacy)} Input is not correct.");
}
}
}
- FactoryAdded.cs -
internal class FactoryAdded : FactoryAbstract
{
private int _oil;
private int _output2;
public override void Run()
{
var words = _input!.Split(' ');
if (words.Length == 3)
{
_isInputCorrect = true;
_wood = int.Parse(words[0]);
_steel = int.Parse(words[1]);
_oil = int.Parse(words[2]);
_output = _wood + _steel;
_output2 = _steel + _oil;
}
else
{
_isInputCorrect = false;
_output = 0;
_output2 = 0;
}
}
public override void Stop()
{
if (_isInputCorrect)
{
Console.WriteLine($"{nameof(FactoryAdded)} Output1: {_output} Output2: {_output2}");
}
else
{
Console.WriteLine($"{nameof(FactoryAdded)} Input is not correct.");
}
}
}
두 생산라인을 가동하기 위해 ProgramInherited.cs를 바꿔 ProgramAbstract.cs에 담자.
- ProgramAbstract.cs -
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
var lines = new List<FactoryAbstract>
{
new FactoryLegacy(),
new FactoryAdded(),
};
foreach (var line in lines)
{
line.GetReady();
line.Run();
line.Stop();
}
Console.WriteLine("Bye.");
실행하니 작동한다. IFactory ← FactoryAbstract, FactoryAbstract ← FactoryLegacy, FactoryAbstract ← FactoryAdded 이렇게 물려받았다. 재활용한 코드가 추상 클래스 FactoryAbstract에 있다.
의존성 주입 이용하기
앞의 두 방법은 기존 생산라인 구현 뒤에야 생산라인 증설을 알게 된 경우이다. 이번엔 생산라인 증설이 예정되어 있을 때, 첫 Factory Line을 구현해보자.
두 생산라인은 세 가지 메서드를 가지고 있는데, 하나는 공유하며 두 개는 기능이 다르다. 기능이 다른 두 개의 메서드를 인터페이스로 만들자.
- IRunnable.cs -
internal interface IRunnable
{
(bool isCorrect, List<int> products) Run(string? input);
}
- Stoppable.cs -
internal interface IStoppable
{
void Stop(bool isCorrect, params int[] outputs);
}
그리고 이 두 인터페이스를 IFactoryByDI.cs에 주입하고 이를 물려받은 FactoryAbstractByDI.cs를 만들자.
- IFactoryByDI.cs -
internal interface IFactoryByDI
{
void GetReady();
void Run();
void Stop(IStoppable stoppable);
IRunnable Runner { get; }
}
- FactoryAbstractByDI.cs -
internal abstract class FactoryAbstractByDI : IFactoryByDI
{
protected string? _input;
protected bool _isCorrect;
public FactoryAbstractByDI(IRunnable runnable)
{
Runner = runnable;
}
public void GetReady()
{
_isCorrect = false;
_input = Console.ReadLine();
}
public abstract void Run();
public abstract void Stop(IStoppable stoppable);
public IRunnable Runner { get; protected set; }
}
IRunnable은 생성자 주입으로 IFactoryByDI.Runner 속성에 할당할 것이고, IStoppable은 메서드 주입으로 했다. 전과 마찬가지로 생산라인 A는 두 재료를 모아 한 제품을, B는 세 재료를 모아 두 제품을 생산한다. 두 재료를 가공하는 클래스 RunnerTwoItems와 세 재료를 가공하는 클래스 RunnerThreeItems를 만들자.
- RunnerTwoItems.cs -
internal class RunnerTwoItems : IRunnable
{
private int _wood, _steel;
public (bool, List<int>) Run(string? input)
{
var words = input!.Split(' ');
bool isCorrect;
var result = new List<int>();
if (words!.Length == NumOfItems)
{
_wood = int.Parse(words[0]);
_steel = int.Parse(words[1]);
result.Add(_wood + _steel);
isCorrect = true;
}
else
{
isCorrect = false;
}
return (isCorrect, result);
}
private static readonly int NumOfItems = 2;
}
- RunnerThreeItems.cs -
internal class RunnerThreeItems : IRunnable
{
private int _wood, _steel, _oil;
public (bool, List<int>) Run(string? input)
{
var words = input!.Split(' ');
bool isCorrect;
var result = new List<int>();
if (words!.Length == NumOfItems)
{
_wood = int.Parse(words[0]);
_steel = int.Parse(words[1]);
_oil = int.Parse(words[2]);
result.Add(_wood + _steel);
result.Add(_steel + _oil);
isCorrect = true;
}
else
{
isCorrect = false;
}
return (isCorrect, result);
}
private static readonly int NumOfItems = 3;
}
생산결과를 출력하는 Stopper를 만들자.
- Stopper.cs -
internal class Stopper : IStoppable
{
public void Stop(bool isCorrect, params int[] outputs)
{
if (isCorrect)
{
Console.Write($"{nameof(Stopper)} Output: ");
foreach (var output in outputs)
{
Console.Write($"{output} ");
}
Console.WriteLine();
}
else
{
Console.WriteLine($"{nameof(Stopper)} Input is not correct.");
}
}
}
이제 생산라인 FactoryA와 FactoryB를 만들자.
- FactoryA.cs -
internal class FactoryA : FactoryAbstractByDI
{
private int _output;
public FactoryA(IRunnable runnable) : base(runnable) { }
public override void Run()
{
(_isCorrect, var result) = Runner.Run(_input);
_output = result[0];
}
public override void Stop(IStoppable stoppable)
{
stoppable.Stop(_isCorrect, _output);
}
}
- FactoryB.cs -
internal class FactoryB : FactoryAbstractByDI
{
private int _output1, _output2;
public FactoryB(IRunnable runnable) : base(runnable) { }
public override void Run()
{
(_isCorrect, var result) = Runner.Run(_input);
_output1 = result[0];
_output2 = result[1];
}
public override void Stop(IStoppable stoppable)
{
stoppable.Stop(_isCorrect, _output1, _output2);
}
}
두 생산 라인을 가동하기 위해 ProgramDI.cs를 만들자.
- ProgramDI.cs -
Console.WriteLine("Hello, World!");
var lines = new List<FactoryAbstractByDI>
{
new FactoryA(new RunnerTwoItems()),
new FactoryB(new RunnerThreeItems()),
};
var stopper = new Stopper();
foreach (var line in lines)
{
line.GetReady();
line.Run();
line.Stop(stopper);
}
Console.WriteLine("Bye.");
실행하니 작동한다. 위의 두 방법보다 의존성 주입으로 구현하니 더 복잡하다. 그러나 복잡한 문제를 풀 땐, 어려운 방법으로 해야 쉽다는 경구가 생각난다. 한 파일 당 라인 수가 줄어들었음을 보라. 이것이 코드 재활용 아닐까 생각한다.
다음 글 '結’에서 생산라인을 증설하는 다섯 가지 방법에 대해 간략히 비교해보겠다.