본문 바로가기
전공/C# 프로그래밍

7강. 객체지향 프로그래밍과 클래스 (3)상속성

by 임 낭 만 2023. 4. 17.

상속성

Introduction to 상속 (Inheritance)

  • 현실 세계에서 많은 객체가 계층적 관계를 따름
    • 한 객체가 다수의 다른 객체의 공통된 특성을 가지고 있음
  • 예를 들어, 노트북, 데스크톱, 넷북이라는 타입을 정의
    • 공통적으로 부팅, 전원 끄기, 리셋 등 컴퓨터로의 공통된 동작 수행
    • 상속이라는 개념이 없으면, 각각 개별적으로 메서드와 상태 값 정의

상속 (Inheritance)

  • 클래스는 다른 클래스로부터 유산을 물려받을 수 있음
    • 즉, 필드나 메소드, 프로퍼티 같은 (클래스의)멤버를 물려받음
    • 상속하는 쪽을 부모(또는 기반) 클래스, 상속 받는 쪽을 자식(또는 파생) 클래스
    • 클래스 이름 옆에 콜론(:) 과 상속받을 클래스 이름 작성
    • “protected” 접근 제한자는 클래스 멤버를 외부에 접근 차단하면서 자식에게는 허용
class 기반 클래스
{
    // 멤버 선언
}
class 파생 클래스 : 기반 클래스
{
    //아무 멤버를 선언하지 않아도 기반 클래스의 모든 것을 물려받아 갖게 됨.
    //단, private로 선언된 멤버는 예외
}

상속 : 다른 클래스로부터 코드를 물려받는 것

물려받는 클래스가 물려줄 클래스 지정

파생 클래스 = 자신만의 고유 멤버 + 기반 클래스 멤버

파생 클래스의 수명 주기 : 기반 생성자 → 파생 생성자 → 파생 종료자 → 기반 종료자

기반 클래스의 멤버 호출 → base : 파생 클래스의 생성자에서 기반 클래스 생성자에 매개 변수 전달 (뒤에서 더 설명함)

상속이 불가능한 경우

  • C# 은 단일 상속만 지원
    • 동시에 둘 이상의 부모 클래스로부터 다중 상속은 허용하지 않음
  • sealed 키워드를 이용해 상속이 불가능 하도록 클래스를 선언
    • 의도하지 않은 상속 또는 파생 클래스 구현을 막음
    • 메소드 오버라이딩 제한

부모 / 자식 클래스 (기반 클래스 & 파생 클래스) 사이의 형식 변환

  • 정수형 변수 사이의 형식 변환
    • 범위가 작은 데이터 타입에서 타입으로는 암시적 변환
    • 범위가 데이터 타입에서 작은 타입으로는 명시적 변환

  • 부모/자식 클래스 사이의 형식 변환
    • 자식 클래스의 객체부모 클래스의 객체로 대입은 암시적 변환
    • 부모 클래스에서 자식 클래스로의 변환은 명시적 변환
      • 컴파일 가능하지만 경우에 따라 런타임 에러 발생 할 수 있음

  • 암시적 형변환이 명시적 형변환보다 자주 사용
    • 메소드 오버로딩 사용 때 보다 코드의 생산성 향상

  • 암시적 형변환을 통해 자식 클래스의 객체를 부모 객체의 배열에 저장

using System;

namespace Inheritance
{
    public class Computer
    {        
        protected bool powerOn;
        public void Boot() { Console.WriteLine("Out: Boot"); }
        public void Shutdown() { Console.WriteLine("Out: Shutdown"); }
        public void Reset() { }
    }
    public class Notebook : Computer
    {
        bool fingerScan;
        public bool HasFingerScanDevice(){ return fingerScan; }
        public void CloseLid()
        {
            powerOn = true;
            if (powerOn == true)
            {
                Shutdown();
            }
        }        
    }
    public class Desktop : Computer { }
    public class Netbook : Computer { }
    public class DeviceManager
    {
        public void TurnOff(Computer device)
        {
            device.Shutdown();
        }
    }
    //public class DeviceManager
    //{
    //    public void TurnOff(Notebook notebook) {/* ... */}
    //    public void TurnOff(Desktop desktop) {/* ... */}
    //    public void TurnOff(Netbook netbook) {/* ... */}
    //}
    class MainApp
    {
        static void Main(string[] args)
        {
            Notebook note1 = new Notebook();
            Computer pc1 = note1;            // 암시적 형변환 가능
            pc1.Boot();
            pc1.Shutdown();

            //Computer pc2 = new Computer();            
            //Notebook note2 = (Notebook)pc2;   // 명시적 형변환, 컴파일은 가능, but 런타임 오류 가능

            Notebook note3 = new Notebook();
            // 부모 타입으로 암시적 형변환
            Computer pc3 = note3;
            
            // 다시 본래 타입으로 명시적 형변환
            Notebook note4 = (Notebook)pc3;            
            note4.CloseLid();

            Notebook notebook = new Notebook();
            Desktop desktop = new Desktop();
            Netbook netbook = new Netbook();

            DeviceManager manager = new DeviceManager();
            manager.TurnOff(notebook);
            manager.TurnOff(desktop);
            manager.TurnOff(netbook);

            Computer[] machines = new Computer[] { new Notebook(), new Desktop(), new Netbook() }; // 암시적 형변환
            DeviceManager manager2 = new DeviceManager();
            foreach (Computer device in machines)
            {
                manager2.TurnOff(device);
            }
        }
    }
}

as, is 연산자

형 변환 연산자

  • 명시적 형식변환 대신 as 연산자 사용을 권장
    • 형식변환에 실패해도 예외 발생하지 않기 때문에 코드 관리가 수월
  • as 연산자는 참조형 변수에 대해서만 적용
  • is 연산자는 참조형식 뿐 아니라 값 형식에도 사용 가능

using System;

namespace TypeCasting
{
    class Mammal
    {
        public void Nurse() 
        {
            Console.WriteLine("Nurse()");
        }
    }
    class Dog : Mammal 
    {
        public void Bark() 
        {
            Console.WriteLine("Bark()");
        }
    }
    class Cat : Mammal
    {
        public void Meow() 
        {
            Console.WriteLine("Meow()");
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            Mammal mammal = new Dog();
            Dog dog;
            if (mammal is Dog)
            { // 객체가 Dog 임을 확인, 안전한 형변환
                dog = (Dog)mammal;
                dog.Bark();
            }

            Mammal mammal2 = new Cat();
            Cat cat = mammal2 as Cat;
            // 만약 형식변환 실패했다면 cat은 null
            if (cat != null)
            { 
                cat.Meow();
            }

            Cat cat2 = mammal as Cat;
            if (cat2 != null)
                cat2.Meow();
            else
                Console.WriteLine("cat2 is not a Cat");
        }
    }
}

부모 / 자식 클래스 생성자와 종료자 호출

  • 자식 클래스는 객체를 생성할 때 부모 클래스의 생성자를 호출 후에 자신의 생성자 호출
  • 객체가 소멸할 때 자신의 종료자 호출 후 부모 클래스 종료자 호출
using System;

namespace Base
{
    class Base
    {
        public void BaseMethod()
        {/* ... */}
        public Base()
        {
            Console.WriteLine("Base()");
        }
        ~Base()
        {
            Console.WriteLine("~Base()");
        }
    }
    class Derived : Base
    {
        public Derived()
        {
            base.BaseMethod();		//base 키워드를 통해 기반 클래스에 접근 가능
            Console.WriteLine("Derived()");
        }
        ~Derived()
        {
            Console.WriteLine("~Derived()");
        }
    }
    class MainApp
    {
        public static void Test()
        {
            Derived derived = new Derived();
        }
        static void Main(string[] args)
        {
            Test();            
            GC.Collect (); // 가비지 컬렉터 수행
            GC.WaitForPendingFinalizers();  // 종료자 큐 처리동안 wait
        }
    }
}

상속받는 경우 생성자로 인한 오류

  • 자식 클래스는 객체를 생성할 때 부모 클래스의 생성자를 자동 호출
  • 자동 호출되는 부모 클래스의 생성자가 매개변수를 가지고 있지만 입력 값을 전달하지 못할 때 에러 발생
  • base 키워드
    • 부모 클래스를 가리킴

base() 생성자

  • base() 는 부모 클래스의 생성자를 나타냄
  • base() 에 매개변수를 넘겨 호출
    • 매개변수를 기본 값으로 초기화
    • 자식 클래스의 생성자에 입력 받은 매개변수 값을 base() 로 연계하여 사용

using System;

namespace BaseConstructor
{
    class Base
    {
        protected string Name;
        public Base(string Name)
        {
            this.Name = Name;
            Console.WriteLine($"{this.Name}.Base()");
        }
        ~Base()
        {
            Console.WriteLine($"{this.Name}.~Base()");
        }
        public void BaseMethod()
        {
            Console.WriteLine($"{this.Name}.BaseMethod()");
        }
    }
    class Derived : Base
    {
        public Derived(string Name) : base(Name) 
        {
            Console.WriteLine($"{this.Name}.Derived()");
        }
        ~Derived()
        {
            Console.WriteLine($"{this.Name}.~Derived()");
        }
        public void DerivedMethod()
        {
            Console.WriteLine($"{this.Name}.DerivedMethod()");
        }
    }
    class MainApp
    {
        public static void Test()
        {
            Base a = new Base("a");
            a.BaseMethod();

            Derived b = new Derived("b");
            b.BaseMethod();
            b.DerivedMethod();
        }
        static void Main(string[] args)
        {
            Test();
            GC.Collect(); // 가비지 컬렉터 수행
            GC.WaitForPendingFinalizers();  // 종료자 큐 처리동안 wait
        }
    }
}

모든 클래스의 부모 : object

  • C# 에서 정의되는 모든 클래스의 부모는 object
  • object 클래스는 아래 네 개의 public 메소드 포함

using System;

namespace Object
{
    public class Computer
    {
        protected bool powerOn;
        public void Boot() { Console.WriteLine("Out: Boot"); }
        public void Shutdown() { Console.WriteLine("Out: Shutdown"); }
        public void Reset() { }
    }
    public class Notebook : Computer
    {
        bool fingerScan;
        public bool HasFingerScanDevice() { return fingerScan; }
        public void CloseLid()
        {
            powerOn = true;
            if (powerOn == true)
            {
                Shutdown();
            }
        }
    }
    public class Desktop : Computer { }
    public class Netbook : Computer { }
    public class DeviceManager
    {
        public void TurnOff(Computer device)
        {
            device.Shutdown();
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            Computer computer = new Computer();
            object obj1 = computer;
            Computer pc1 = obj1 as Computer;

            Notebook notebook = new Notebook();
            object obj2 = notebook;
            Notebook pc2 = obj2 as Notebook;
        }
    }
}

댓글