ch08 인터페이스와 추상 클래스

2020. 1. 17. 18:00C# 언어/이것이 C# 이다. 책정리

반응형

 

 

챕터 08 '인터페이스와 추상 클래스'  입니다.

인터페이스와 추상 클래스의 차이점과 사용방법을 알아봅니다.

 

'이것이 C#이다' 교재를 바탕으로 정리했습니다.

 

이전 정리 글


ㅇ 인터페이스의 선언

인터페이스는 PC에 사용되는 USB 포트와 같은 역할이다.

USB 플래시 메모리를 꽂아 넣으면 저장 장치로 쓸 수 있고,

키보드나 마우스를 꽂으면 입력 장치로도 쓸 수 있다.

말하자면 클래스가 따라야 하는 약속이 되는 셈이다.

 

interface 인터페이스 이름
{
    반환 형식 메서드 이름 1( 매개 변수 목록 );
    반환 형식 메서드 이름 2( 매개 변수 목록 );
    반환 형식 메서드 이름 3( 매개 변수 목록 );
    반환 형식 메서드 이름 4( 매개 변수 목록 );
}

이처럼 interface 키워드를 사용하여 선언한다.

 

인터페이스의 특징은 다음과 같다.

  1. 메서드, 이벤트, 인덱서, 프로퍼티만 가질 수 있다.
  2. 구현부가 존재하지 않는다.
  3. 클래스는 접근 제한 한정자를 수식하지 않으면 private로 선언되지만 인터페이스는 public으로 선언된다.
  4. 인터페이스는 인스턴스를 만들 수 없다.

 

인터페이스의 사용
interface ILogger // 인터페이스 생성
{
    void WriteLog( string log );
}

class ConsoleLogger : ILogger // 인터페이스 상속
{
    public void WriteLog( string message )
    {
        Console.WriteLine(
            "{0} {1}", DateTime.Now.ToLocalTime(), message);
    }
}

void Main()
{
    ILogger logger = new ConsoleLogger();
    logger.WriteLog( "Hellow, World!");
}

위 코드에서 보는 것처럼 인터페이스는 인스턴스를 못 만들지만,

참조는 만들 수 있습니다. 인터페이스의 작명법은 

이름 앞에 'I'를 붙이는 것이 관례입니다.

 

이용 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
using System;
using static System.Console;
using System.Text;
using System.IO;
 
namespace baekjoon
{
    interface ILooger
    {
        void WriteLog(string message);
    }
 
    class ConsoleLogger : ILooger
    {
        public void WriteLog(string message)
        {
            WriteLine($"{DateTime.Now.ToLocalTime()} {message}");
        }
    }
 
    class FileLogger : ILooger
    {
        private StreamWriter writer;
 
        public FileLogger(String path)
        {
            writer = File.CreateText(path);
            writer.AutoFlush = true;
        }
 
        public void WriteLog(string message)
        {
            writer.WriteLine($"{DateTime.Now.ToShortTimeString()} {message}");
        }
    }
 
    class ClimateMonitor
    {
        private ILooger logger;
        public ClimateMonitor(ILooger logger)
        {
            this.logger = logger;
        }
 
        public void start()
        {
            while(true)
            {
                Write("온도를 입력해주세요.");
                string temperature = Console.ReadLine();
                if (temperature == "")
                {
                    break;
                }
 
                logger.WriteLog("현재 온도 : " + temperature);
            }
        }
    }
 
    class MainApp
    {
        static void Main()
        {
            ClimateMonitor monitor = new ClimateMonitor(
                new FileLogger("MyLog.txt"));
 
            monitor.start();
        }
    }
}

ㅇ 인터페이스를 상속하는 인터페이스

기존의 인터페이스에 새로운 기능을 추가한 인터페이스를 만들고 싶을 때

인터페이스를 상속하는 인터페이스를 만들면 됩니다.

기존 인터페이스에 새로운 기능을 추가하면 클래스는 반드시 인터페이스의 "모든" 메서드와 프로퍼티를 구현해야 됩니다.

인터페이스의 사소한 수정이라도 이루어지면 기존 상속받는 클래스들은 소스 코드를 빌드할 때 컴파일 에러를 죽죽 내뱉을 겁니다.

 

interface A {}
interface B : A {}

클래스와 동일하게 ' : ' 기호를 이용하여 상속함 당연하지만 부모 인터페이스에 선언된 모든 것을 그대로 물려받음


 

ㅇ 여러 개의 인터페이스, 한꺼번에 상속하기

클래스는 여러 클래스를 한꺼번에 상속할 수 없습니다.

   이와 같은 이른바 "죽음의 다이아몬드" 문제 때문인데

ComboDrive 입장에서는 CDBurner의 burn()을 상속받는지 DVDBurner의 burn() 알지 못합니다.

인터페이스는 내용이 아닌 외형을 물려줍니다. 속은 어떨지 몰라도 겉모습만큼은 정확하게 자신을 닮기를 강제합니다.

따라서 죽음의 다이아몬드 같은 문제도 발생하지 않습니다.

 

이용한 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace p298
{
    interface IRunnable
    {
        void Run();
    }
    interface IFlyable
    {
        void Fly();
    }
    class FlyingCar : IRunnable, IFlyable // 다중 상속
    {
        public void Fly()
        {
            WriteLine("Fly! Fly!");
        }
 
        public void Run()
        {
            WriteLine("Run! Run!");
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            FlyingCar car = new FlyingCar();
            car.Run();
            car.Fly();
 
            IRunnable runnable = car as IRunnable;
            runnable.Run();
 
            IFlyable flyable = car as IFlyable;
            flyable.Fly();
        }
    }
}

ㅇ 추상 클래스 : 인터페이스와 클래스 사이

추상 클래스는 인터페이스와는 달리 "구현"을 가질 수 있습니다.

하지만 클래스와 달리 인스턴스를 가질 수는 없습니다.

 

abstract class 클래스 이름
{
    // 클래스와 동일하게 정의
}

abstract 한정자와 class 키워드를 이용해서 선언합니다.

클래스와 다른 점은 인스턴스를 가질 수 없는 것과 

추상 메서드를 가질 수 있다는 점입니다.

 

추상 메서드는 추상 클래스가 한편으로 인터페이스의 역할도 할 수 있게 해주는 장치입니다.

구현을 갖지는 못하지만 파생 클래스에서 반드시 구현하도록 강제하거든요.

 

그러면 추상 메서드의 기본 접근성은 어떻게 될까요?

인터페이스처럼 public? 클래스처럼 private?

답은 "둘 다"입니다.

추상 클래스나 클래스는 그 안에서 선언되는 모든 필드의 접근 한정자를 명시하지 않으면 private입니다.

하지만 "약속" 역할을 하는 추상 메서드가 private이면 안 되겠죠? (정의가 매번 돼야 되니깐요!)

그래서 추상 메서드는 반드시 public, protected, internal, protected internal 한정자 중 하나로 강요합니다.

 

이용한 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace p301
{
    abstract class AbstractBase
    {
        protected void PrivateMethodA()
        {
            WriteLine("AbstractBase.PrivateMethodA()");
        }
        public void publicMethodA()
        {
            WriteLine("publicMethodA");
        }
        public abstract void AbstractMethodA(); // 추상 메소드
    }
    class Derived : AbstractBase
    {
        public override void AbstractMethodA()
        {
            WriteLine("Derived.AbstractMethodA()");
            PrivateMethodA();
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            AbstractBase obj = new Derived();
            obj.AbstractMethodA();
            obj.publicMethodA();
        }
    }
}

ㅇ 추상클래스가 무슨 쓸모가 있지?

추상 클래스는 일반 클래스가 가질 수 있는 구현과 더불어 추상 메서드를 가지고 있습니다.

추상 메서드는 추상 클래스를 사용하는 프로그래머가 그 기능을 정의하도록 강제하는 장치입니다.

 

클래스를 통해서도 똑같은 일을 할 수 있습니다.

그냥 메서드를 선언하고 클래스에 대한 매뉴얼을 작성해서 배포합니다.

"이 클래스는 직접 인스턴스 화하지 말고 파생 클래스를 만들어 사용하세요."

라는 식으로요. 그러나 이를 프로그래머들이 준수하도록 강제시킬 수 없습니다.

하지만! 추상 클래스를 이용한다면 이런 설명이 필요 없습니다. 추상 클래스, 메서드가 이런 설명을 담고 있으니깐요.

 

내가 만든 추상 클래스를 이용하는 다른 프로그래머가 파생 클래스를 만들어야 하며 모든 추상 메서드를 구현해야

한다는 사실을 잊어버린다 해도, 컴파일러가 상기시켜줄 것입니다.

 

이것이 우리가 추상 클래스를 사용하는 이유입니다.


 

반응형