외로운 Nova의 작업실

c# 언어 공부 - 5(인터페이스,추상클래스,프로퍼티) 본문

Programming/C#

c# 언어 공부 - 5(인터페이스,추상클래스,프로퍼티)

Nova_ 2022. 5. 3. 16:07

안녕하세요. 저번시간에 이어 오늘은 c#의 인터페이스에대해 알아보겠습니다.

 

<인터페이스>

인터페이스는 프로그래머들간의 약속을 위해 탄생했습니다.

"우리 robot 객체는 움직이는 move 메소드와 말하는 speak 메소드는 꼭 만들자"

라고 메소드의 이름과 내용등을 정합니다.

그래서 인터페이스의 정의는 아래와 같습니다.

  interface IRobot
    {
        void Move(); //움직이는 메소드
        void Speak(); //말하는 메소드
    }

인터페이스는 메소드, 이벤트, 인덱서, 프로퍼티만을 약속할 수 있습니다.

 

프로그래머는 이 인터페이스를 '상속'하여 사용하게됩니다.

상속하게되면 '무조건' 인터페이스에서 약속한 메소드는 꼭 구현해야합니다.

구현을 안하고 컴파일하게되면 컴파일러가 오류를 냅니다.

상속하는 것은 클래스랑 똑같습니다.

코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {

        }

    }

    interface IRobot
    {
        void Move(); //움직이는 메소드
        void Speak(); //말하는 메소드
    }

    class Robot : IRobot
    {
        public void Move()
        {
            Console.WriteLine("움직임");
        }

        public void Speak()
        {
            Console.WriteLine("말하다");
        }
    }


}

위와같이 IRobot 인터페이스를 상속받은 건 IRobot의 약속을 따르겠다는 것이고, 상속받은 Robot 클래스는 그 약속에따라 무조건 Move(), Speak() 메소드를 구현해야합니다.

 

인터페이스는 인터페이스를 상속 할 수도 있습니다.

상속하는 법은 클래스와 다를 것 없습니다.

코드를 봅시다.

interface IRobot
    {
        void Move(); //움직이는 메소드
        void Speak(); //말하는 메소드
    }
    
    interface IRobotStudy : IRobot
    {
        void Study(); //공부시켜주는 메소드
    }

IRobotStudy 인터페이스는 Move(), Speak(), Study()를 무조건 갖자는 인터페이스가 됩니다.

 

<다중상속 인터페이스>

클래스는 다중상속이 안됩니다.

먼저 코드부터 보시죠.

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Robot robot = new Robot();

            robot.Move(); //어떤결과가 나올 것같나요?
        }

    }

 
    class Robot : arm, reg
    {
        
    }

    class arm
    {
        public void Move()
        {
            Console.WriteLine("팔을 움직이다");
        }
    }

    class reg
    {
        public void Move()
        {
            Console.WriteLine("다리를 움직이다.");
        }
    }


}

위코드는 먼저 잘못된 코드입니다.

클래스는 다중상속이 안되는데 되게했죠.

그럼 저 메인함수에서 robot.Move()의 결과는 팔을 움직이는 것일까요 다리를 움직이는 것일까요?

이러한 모호성때문에 클래스는 다중상속을 금지시켰습니다.

하지만 만약 arm클래스와 reg클래스가 인터페이스(객체가 아닌 약속)이라면, 약속을 여러개 상속 받게 만들기 위해서 다중상속을 허용시켯습니다.

코드를 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Robot robot = new Robot();

            robot.MoveReg(); //어떤결과가 나올 것같나요?
        }

    }

 
    class Robot : arm, reg
    {
        public void MoveArm()
        {
            Console.WriteLine("팔을 움직이다");
        }
        public void MoveReg()
        {
            Console.WriteLine("다리를 움직이다.");
        }
    }

    interface arm
    {
        public void MoveArm()

    }

    interface reg
    {
        public void MoveReg()
    }


}

 

<인터페이스의 기본 구현 메소드>

인터페이스는 약속을 위해 메소드의 이름만 정의를 하는데, 미리 메소드를 기본적으로 구현할 수 도 있습니다.

먼저 코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            IRobot irobot = new StudyRobot();

            irobot.MoveArm(); //실행 가능
            irobot.MoveReg(); //실행 가능

            StudyRobot studyrobot = new StudyRobot();

            studyrobot.MoveArm(); //실행 가능
            //studyrobot.MoveReg(); 는 오류발생

        }

    }

 
    interface IRobot 
    {
        public void MoveArm();
        public void MoveReg() //기본 구현은 메소드와 동일하게 정의함
        {
            Console.WriteLine("다리를 움직이다.");
        }
    }

    class StudyRobot : IRobot
    {
        public void MoveArm()
        {
            Console.WriteLine("팔을 움직이다");
        }
    }



}

위 코드의 주석처럼 기본 구현은 메소드와 동일하게 정의할 수 있습니다.

다만 기본구현 메소드는 main함수 안의 내용처럼 인터페이스 형식에서만 사용이 가능합니다.

파생 클래스 형식에서 사용하게되면 에러가 발생하게됩니다.

 

<추상클래스>

추상클래스는 인터페이스(약속)을 좀 더 보강하기위해서 만들어진 약속입니다.

인터페이스는 메소드를 구현하지않고 만들어달라고하지만, 추상 클래스는 구현도 가능하고 추상메소드로 만들어달라고할 수도 있습니다.

프로그래머들에게 몇개의 메소드들을 직접 만들기로하고 나머지 것들만 만들어달라고 약속 하기위해서 만들어진게 추상클래스입니다.

또한 인터페이스는 메소드의 접근한정자를 직접 쓰지않으면 public으로 되지만,

추상클래스는 직접 쓰라고 강요합니다.

추상클래스는 abstract 키워드를 사용하여 정의합니다.

먼저 코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            StudyRobot robot = new StudyRobot();

            robot.MoveArm();
            robot.MoveReg();
        }

    }

 
    abstract class IRobot //abstract 키워드를 사용하여 추상클래스 정의
    {
        public void MoveArm() //구현할때는 abstract 키워드 사용안함
        {
            Console.WriteLine("팔을 움직이다");
        }
        public abstract void MoveReg();//추상 메소드는 abstract키워드 사용, 선언만 가능 -> 파생클래스의 구현 강요함
    }

    class StudyRobot : IRobot
    {
        public override void MoveReg()
        {
            Console.WriteLine("다리를 움직여서 뛰는걸 가르치다.."); //추상클래스에서 추상메소드 구현
        }
    }



}

주석을 읽으면서 코드를 읽어보시면 이해가 되실겁니다.

 

<프로퍼티>

객체지향 프로그래밍에서 중요하게 생각하는 은닉성은 객체의 필드를 직접 호출해서 수정하지 못하게 하는 것입니다.

보통은 필드의 값을 얻는 get()메소드와 필드의 값을 수정하는 set()메소드를 이용해서 수정하게 만들지요.

코드를 먼저 보시죠.

  class Dog
    {
        private string name;

        public string GetName()
        {
            return name;
        }

        public void SetName(string name)
        {
            this.name = name;
        }
    }

위 코드처럼 클래스의 필드 name을 직접적으로 수정하지 못하게합니다.

하지만 직접적으로 수정하는 코드는 너무 간결하고 메소드를 이용한 코드는 조금은 복잡합니다.

코드먼저 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Dog dog = new Dog();

            dog.name = "Kong"; // 직접 수정하는 코드

            dog.SetName("kong"); //메소드를 사용해서 수정하는 코드
        }

    }

 
    class Dog
    {
        public string name;

        public string GetName()
        {
            return name;
        }

        public void SetName(string name)
        {
            this.name = name;
        }
    }



}

직접 수정하는 코드는 간결하지만 은닉성이 떨어져서 필드가 오염될 수 있고,

메소드를 사용하는 코드는 조금 복잡하지만 은닉성이 강해서 필드가 안전할 수 있습니다.

이러한 괴리감을 줄이기위해서 프로퍼티가 사용이됩니다.

프로퍼니는 은닉성과 코드를 간결하게 사용할 수 있는 '필드'입니다.

먼저 코드를 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Dog dog = new Dog();

            dog.name = "Kong"; // set함수 발동
            
            Console.WriteLine(dog.name); //get함수 발동
            
        }

    }


    class Dog
    {
        public string name //name이란 프로퍼티 선언
        {
            get //Dog.name 하면 get됨
            {
                return name;
            }
            set //Dog.name = Value 값하면 set됨
            {
                name = value;
            }
        }
    }



}

위 코드처럼 Dog를 name이란 프로퍼티를 사용하여 은닉성과 간결성을 동시에 가져갈 수 있습니다.

프로퍼티는 public으로 접근한정자를 지정해줘야합니다.

만약 프로퍼티의 set 접근자를 지정하지않으면 프로퍼티의 값을 바꿀 수 없습니다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Dog dog = new Dog();

            //dog.name = "Kong"; -> set함수가 없어서 불가능한 코드입니다.
            
            Console.WriteLine(dog.name); //get함수 발동
            
        }

    }


    class Dog
    {
        public string name //name이란 프로퍼티 선언
        {
            get //Dog.name 하면 get됨
            {
                return name;
            }
        }
    }



}

 

프로퍼티는 좀 더 간결하게 쓸 수있습니다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Dog dog = new Dog();

            dog.name = "Kong"; 
            
            Console.WriteLine(dog.name); //get함수 발동
            
        }

    }


    class Dog
    {
        public string name //name이란 프로퍼티 선언
        {
            get; set;//좀더 간결하게 선언 기존 코드와 동일

        }
    }



}

위 코드는 전에 쓴 코드와 동일한 기능을 가지고 있습니다.

 

<생성자와 프로퍼티>

생성자를 쓸때 항상매개변수는 함수의()구문 사이에 넣어줘야합니다.

하지만 프로퍼티를 사용하면 매개변수를 다르게 값을 줄 수 있습니다.

코드먼저 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Dog dog = new Dog()
            {
                name = "Kong", //콤마를 마지막에 붙여합니다
                age = 12
            }; //마지막에 세미콜론을 붙여야합니다.

         
            
        }

    }


    class Dog
    {
        public string name //name이란 프로퍼티 선언
        {
            get; set;//좀더 간결하게 선언 기존 코드와 동일

        }

        public int age
        {
            get; set;
        }
    }
}

위와 같이 생성자 메소드 ()구문안이 아닌 {} 구문 안에 직접 변수명을 활용하여 값을 전달 할 수 있습니다.

 

프로퍼티는 또한 생성자에서만 값을 변경하게 할 수도 있습니다.

이는 성적표, 범죄기록, 국가기록등의 자료를 만들때 유용합니다.

구현은 아래 코드와 같습니다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            Dog dog = new Dog()
            {
                name = "Kong",
                age = 12
            };

            //dog.name = "doong" 코드 사용불가 

         
            
        }

    }


    class Dog
    {
        public string name //name이란 프로퍼티 선언
        {
            get; init;//set이 아닌 init을 넣으면 생성자에서만 set이 가능하다

        }

        public int age
        {
            get; init;//set이 아닌 init을 넣으면 생성자에서만 set이 가능하다
        }
    }
}

init이 사용된 프로퍼티를 초기화 전용 자동 구현 프로퍼티라고도 합니다.

 

<레코드>

먼저 불변형식을 만들고 사용할때는 많은 불편함이 따릅니다.

불변형식을 만들때는 readonly 키워드를 이용해서 클래스나 구조체를 선언해야합니다.

이렇게 선언된 클래스는 참조형식이고, 구조체는 값형식입니다.

참조형식은 값의 복사는 간편하지만(해당 구조체의 주소만 주면되니) 값의 비교는 번거롭습니다.(주소를 통해 값을 비교해야함)

값의 형식은 비교는 간편하지만(값만 비교하면됨), 복사는 번거롭습니다.(값이 100개라면 일일이 100개 다 복사해야함)

 

이러한 값의 복사와 값의 비교를 효율적으로 하기 위해서 recode라는 새로운 형식을 만들게 됩니다.

먼저 코드부터 봅시다.

  record RBank
    {
        public string from;{ get; init; } //돈을 보내는사람
        public string to{ get; init; }; //돈을 받는 사람
        public int amount{ get; init; }; //금액

        public void Write()
        {
            Console.WriteLine($"{from} -> {to} : {amount}");
        }
    }

레코드는 record 키워드를 사용하여 클래스와 비슷하게 정의하고 프로퍼티를 사용하게됩니다.

그렇다면 레코드를 복사해보도록 합시다.

복사는 with 키워드를 이용해서 복사합니다.

먼저 코드부터 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            RBank bank1 = new RBank()
            {
                from = "alice",
                to = "tomas",
                amount = 300
            };

            RBank bank2 = bank1 with { to = "angel" }; //bank1의 기록에서 받는 사람을 angel로 고쳐서 bank2에 복사
            RBank bank3 = bank2 //bank2의 기록이 bank3로 다 전달
            bank2.Write();//alice -> angel : 300이 출력됨

        }

    }

    record RBank
    {
        public string from; //돈을 보내는사람
        public string to; //돈을 받는 사람
        public int amount; //금액

        public void Write()
        {
            Console.WriteLine($"{from} -> {to} : {amount}");
        }
    }
   
}

값의 복사는 어짜피 불변이기때문에 주소를 복사하는(참조)형식으로 진행이됩니다.

이제 값의 비교를 해봅시다.

비교는 Equals() 메소드를 이용해서 비교하게됩니다.

먼저 코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            RBank bank1 = new RBank()
            {
                from = "alice",
                to = "tomas",
                amount = 300
            };

            RBank bank2 = bank1 with { to = "angel" }; //bank1의 기록에서 받는 사람을 angel로 고쳐서 bank2에 복사
            RBank bank3 = bank2; //bank2의 기록이 bank3로 다 전달

            if (bank3.Equals(bank2))//bank2가 bank3와 같다면 true반환 아니라면 false반환
            {
                Console.WriteLine("은행3이 은행2로부터 제대로 복사되었습니다.");
            }
            bank2.Write();//alice -> angel : 300이 출력됨

        }

    }

    record RBank
    {
        public string from; //돈을 보내는사람
        public string to; //돈을 받는 사람
        public int amount; //금액

        public void Write()
        {
            Console.WriteLine($"{from} -> {to} : {amount}");
        }
    }
   
}

위의 실행결과는 아래와 같습니다.

record를 이용한 불변객체

 

<무명형식>

불변형식중에는 무명형식이라는 것이 있습니다.

생성자가 없고 형식만 있을 뿐입니다.

먼저 코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
            var myinterface = new { name = "Kong", age = "17" }; //무명형식선언

            Console.WriteLine($"{myinterface.name}, {myinterface.age}"); //식별자로 사용가능

        }

    }


   
}

따로 클래스나 구조체를 선언하여 생성자로 생성하지않고 바로 형식을 선언해버립니다.

식별자를 통해 사용이 가능합니다.

무명 형식은 '불변 형식'입니다.

처음에 할당한 값을 바꾸지 못합니다.

 

<인터페이스의 프로퍼티>

인터페이스도 프로퍼티를 가질 수 있습니다.

구현은 하지않고 선언만 한다음 파생클래스는 프로퍼티의 set, get함수를 구현해야합니다.
코드를 봅시다.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
           

        }

        interface Iproduct
        {
            public string name { get; set; } //선언만함

        }

        class product : Iproduct
        {
            public string name
            {
                get //프로퍼티 직접 구현
                {
                    return name;
                }
                set //프로퍼티 직접 구현
                {
                    name = value;
                }
            }
        }

    }


   
}

 

<추상클래스의 프로퍼티>

추상클래스의 프로퍼티는 추상 프로퍼티라고 합니다.

추상 프로퍼티는 abstract 키워드로 만들고 선언후 파생클래스에서는 구현을 해줘야합니다.

코드를 보시죠.

using System;

namespace StudyCSharp
{
    class MainApp
    {

        static void Main(string[] args)
        {
           

        }

        abstract class Iproduct
        {
            public abstract string name { get; set; } //추상 프로퍼티

        }

        class product : Iproduct
        {
            public override string name //override로 직접 구현해줘야함
            {
                get
                {
                    return name;
                }
                set
                {
                    name = value;
                }
            }
        }

    }


   
}

이것으로 인터페이스와 추상클래스, 프로퍼티에대한 내용을 마치도록 하겠습니다.

다음시간에는 배열부터 배워보도록 하겠습니다.

천리길도 한걸음부터! 천천히 나아가는 우리가 됩시다!!

Comments