NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 18-1. 인터페이스와 추상 클래스. (Interface and Abstract class)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 오늘부터 알아볼 내용은 추상 클래스와 인터페이스입니다. 이 둘은 OOP의 다형성을 구현하는데 필수이므로 전부 이해하지 못한다 하더라도 기본적인 내용은 꼭 알아두어야 합니다. 누구나 그렇듯이 한번에 OOP를 할 수는 없습니다. 하다보면 자연스럽게 채득되는 기술이기에 꾸준히 연습하고 배워서 내것으로 만들어야 하는거죠. 이런 글을 적고 있는 저도 OOP에 대해서 정의하라고 하면 책에 있는데로 답할수밖에 없습니다. 

     

    보통 깊이 있게 아는 사람일수록 쉽게 설명하는데, 그 깊이라는걸 얻는게 쉽지 않죠. 운도 어느정도 따라야 하구요. 예를들면, 신입일 때 선배가 OOP를 잘해서 그 코드를 매일같이 봐왔거나 엔터프라이즈급 프로젝트의 프레임워크가 OOP에 충실했다거나등등... 접해볼 수 있는 기회가 많을수록 그만큼 성장도 빠른 부분이죠. 그래서 운도 중요하게 작용한다라고 말한겁니다.

     

    다형성(Polymorphism)이란 무엇인가?

    그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다. 반댓말은 단형성(Monomorphism)으로, 프로그램 언어의 각 요소가 한가지 형태만 가지는 성질을 가리킨다.

    [ 출처: 위키백과 ]

     

    위의 설명을 보면 언뜻 이해되지 않을수도 있습니다. 좀 더 쉽게 이야기 하자면, 포유류라는 자료형에는 강아지, 고양이, 소, 돼지와 같은 자료형을 담을 수 있다는 의미입니다. 만약, 조류 자료형이 추가되었다면 강아지는 이곳에 담을 수 없겠죠? 이외에도 메서드를 통한 다형성을 구현할 수 있습니다. Method Overriding인데요. 이는 확장을 통해 다형성을 구현합니다.

    메서드 오버라이딩(재정의)에 대한 자세한 내용은 아래 링크를 참조하세요. 이 글에서는 추상 클래스와 인터페이스만 다루도록 하겠습니다.

    [ 클릭: 17-10. 오버라이드 ]

     

    아래 예제를 통해 추상 클래스를 통한 다형성에 대해서 알아보겠습니다. 그전에 오늘은 4.13일 총선 투표하는 날이죠. 제가 사는곳은 수원대 안에 투표소가 있다고 하는군요. 일단 와이프와 투표하고 와서 나머지 글을 작성하도록 하겠습니다. 여러분도 자신의 주권인 한표를 꼭 행사하시기 바랍니다.

    투표하고 나오는 길에 출구조사 했더니 후라보노 껌 한통을 주는군요. 확실히 노인분들이 많네요^^;

    Program.cs

    using System;
     
    namespace AbstractClass
    {
        public abstract class 자동차
        {
            public abstract int 최고속도 { get; }
            public virtual int 바퀴 { get; protected set; }
            protected void 달리다() { Console.WriteLine("달리다."); }
            public virtual void 달릴수있다()
            {
                Console.WriteLine("달릴 수 있다.");
            }
        }
     
        public class BMW5 : 자동차
        {
            public override int 최고속도 { get; }
            public override void 달릴수있다() { 달리다(); }
            public BMW5(int maximumSpeed)
            {
                최고속도 = maximumSpeed;
                바퀴 = 4;
            }
        }
     
        class MainApp
        {
            static void Main(string[] args)
            {
                자동차 car = new BMW5(300);
                Console.WriteLine($"{car.GetType().Name}는 {car.바퀴}개의 바퀴로 {car.최고속도}km의 속도를 낼 수 있습니다.");
                car.달릴수있다();
                Console.ReadKey();
            }
        }
    }

     

     

    아주 간단한 예제입니다. 이 프로그램은 자동차라는 추상적인 클래스와 이 추상 클래스를 상속 받아서 구현된 BMW5가 있습니다. 자동차란 현실에 존재하지 않지만 현실에 존재하는 어떤 것(Thing)들을 표현할 때 사용합니다. 여기에서 말하는 어떤 것은 BMW 5나 Benz C-Class, Audi RS7과 같이 현실 세계에 존재하는 것들입니다. 그렇기 때문에 추상 클래스인 자동차는 new를 사용하여 인스턴스화 할 수 없습니다. 현실에 존재하지 않으니까요.

     

    위 코드의 7라인은 추상 속성입니다. 이는 파생 클래스에서 무조건 재정의 해야 한다는 의미입니다. 8라인의 경우 가상 속성으로 파생 클래스에서 선택적으로 재정의 할 수 있습니다. 41라인을 보면 자동차에 BMW5를 넣고 있습니다. 왜 이런식으로 하는지 당장은 이해하기 쉽지 않습니다. 그냥 BMW5 자료형에 BMW5를 넣으면 되니까요. 우선 결과를 확인한 후 이 코드를 조금 변형해 보겠습니다.

    l4MdsI4.png

     

     

    위 결과에서 "달리다."가 화면에 찍힌 이유를 이해할 수 있어야 합니다. 자료형은 "자동차"지만 실제 안에는 "BMW5"가 있다는 것입니다. 이제 코드를 변경해 볼까요?

    Program.cs

    using System;
     
    namespace AbstractClass
    {
        public abstract class 자동차
        {
            public abstract int 최고속도 { get; }
            public virtual int 바퀴 { get; protected set; }
            protected void 달리다() { Console.WriteLine("달리다."); }
            public virtual void 달릴수있다()
            {
                Console.WriteLine("달릴 수 있다.");
            }
        }
     
        public class BMW5 : 자동차
        {
            public override int 최고속도 { get; }
            public override void 달릴수있다() { 달리다(); }
            public BMW5(int maximumSpeed)
            {
                최고속도 = maximumSpeed;
                바퀴 = 4;
            }
        }
     
        /// <summary>    
        /// 벤츠사의 트럭입니다.    
        /// </summary>    
        public class BenzActos : 자동차
        {
            public override int 최고속도 { get { return 80; } }
            public override int 바퀴 { get; protected set; }
            public void 달려보자() { Console.WriteLine("달려보자."); }
            public BenzActos(int carWheelCount)
            {
                바퀴 = carWheelCount;
            }
        }
     
        class MainApp
        {
            static void Main(string[] args)
            {
                자동차 car = new BMW5(300);
                Console.WriteLine($"{car.GetType().Name}는 {car.바퀴}개의 바퀴로 {car.최고속도}km의 속도를 낼 수 있습니다.");
                car.달릴수있다();
                Console.WriteLine();
                car = new BenzActos(10);
                Console.WriteLine($"{car.GetType().Name}는 {car.바퀴}개의 바퀴로 {car.최고속도}km의 속도를 낼 수 있습니다.");
                car.달릴수있다();            
                //car.달려보자();            
                Console.ReadKey();        
            }
        }
    }

     

     

    위 코드의 40라인에 BenzActos 클래스를 추가했습니다. 이 클래스는 트럭으로 BMW5 세단과는 여러가지 특성이 틀려지게 됩니다. 그리고 45라인에 있는 "달려보자" 메서드를 유심히 보세요. 이 메서드는 BenzActos에만 존재합니다. 이전 예제와 마찬가지로 car에 BenzActos를 생성했습니다. car 내부에는 BenzActos가 존재하지만 BenzActos의 메서드인 "달려보자"는 실행할 수 없습니다. 그렇기 때문에 71라인은 주석으로 막아 두었습니다. 실제로 어떻게 동작하는지 확인 해볼까요?

    Znl8jD8.png

     

     

    BMW5와는 다르게 base 클래스(추상 클래스인 Car)의 "달릴수있다"가 실행된 것을 알 수 있습니다. 그렇다면, BenzActors의 "달려보자"를 실행하려면 어떻게 해야 할까요? 아래처럼 변경하여 처리하면 됩니다.

    Program.cs

                (car as BenzActos).달려보자();

     

     

    위 코드처럼 생성된 클래스로 캐스팅(형변환)하면 됩니다.

    HjXhoRM.png

     

     

    그렇다면 "왜?"라는 의문이 듭니다. 무엇 때문에 이렇게 복잡하게 만들어야 하는가에 대한 질문이 되겠죠? 어차피 BMW5도 만들어야 하고, BenzActors, Audi A1, A3, A4, A5... 어차피 자동차를 상속 받아서 다 만들어야하니 말입니다. 하지만, 반대로 생각해보세요. 지금은 단순한 예제이므로 대부분 구현하면서 다른 부분을 찾으려고 노력했기 때문입니다. 하지만, 실 세계에 대입해보면 얼마나 편리한지 금방 알게 됩니다. 아래와 같은 시나리오를 들 수 있겠죠.

    BMW5에 약 100개의 속성과 50개의 메서드가 있다고 생각 해보세요. Audi A5를 만들 때 비슷한 속성과 비슷한 메서드를 다시 만들어야 할겁니다. 이 때 자동차 특히 세단이 지녀야 하는 공통된 속성들을 추상화하여 미리 정의해놓고 세단들이 모두 상속받아서 만든다고 하면 상당히 많은 시간과 비용을 절약할 수 있게됩니다. 대부분 자동차의 구동축과 엔진, 점화, 발진, 브레이크 시스템은 비슷할겁니다. 수많은 자동차 모델들을 빠르게 개발할 수 있게 되죠.

     

    아래 예제를 통해 또다른 장점을 확인할 수 있습니다. 그리 큰 노력 없이도 알아볼 수 있기 때문에 다행이군요-_-;

    Program.cs

        class MainApp
        {
            static void Main(string[] args)
            {
                자동차[] cars = { new BMW5(300), new BenzActos(10) };
                foreach (var car in cars)
                {
                    Console.WriteLine($"{car.GetType().Name}는 {car.바퀴}개의 바퀴로 {car.최고속도}km의 속도를 낼 수 있습니다.");
                    car.달릴수있다();
                }
                Console.ReadKey();
            }
        }

     

     

    소스가 많이 간편해지고 각 자동차들의 테스트가 얼마나 용이해 졌는지 쉽게 알 수 있습니다. 지금은 2대의 자동차밖에 없기 때문에 이 코드의 효율성에 대해 크게 느끼지 못할수도 있습니다. 만약, 100대의 자동차를 달리게하는 테스트라면? 그렇죠~ 이 코드는 특별한 수정없이 cars를 파라메터로 받기만 하면됩니다. 그러면 알아서 모든 자동차들이 달리게되며 손쉽게 테스트를 완료할 수 있게됩니다.

     

    결과는 동일합니다.

    ajQ1M3J.png

     

     

    간단하게나마 추상 클래스에 대해 알아보았습니다. 다른 한편으로는 추상화라는 개념이 있습니다. 이는 추상 클래스와는 또다른 개념으로 리팩토링과 관련되어 있습니다. 말장난처럼 보일지도 모르나 상당히 중요한 개념이므로 OOP가 어느정도 마무리되면 리팩토링에 대해서도 다루어 보았으면 좋겠네요. 다음에는 인터페이스에 대해서 알아보겠습니다.

     

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.