코드 재활용과 생산라인 증설 - 轉

코드 재활용과 생산라인 증설 - 轉

이철우

객체지향의 '물려받기’를 이용하여 코드를 재활용하며 생산라인을 증설하자. 다양한 방법이 있겠지만 ‘기존 클래스 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.");

실행하니 작동한다. 위의 두 방법보다 의존성 주입으로 구현하니 더 복잡하다. 그러나 복잡한 문제를 풀 땐, 어려운 방법으로 해야 쉽다는 경구가 생각난다. 한 파일 당 라인 수가 줄어들었음을 보라. 이것이 코드 재활용 아닐까 생각한다.
다음 글 '結’에서 생산라인을 증설하는 다섯 가지 방법에 대해 간략히 비교해보겠다.

2개의 좋아요