[객체지향 프로그래밍] 7장-5 접근한정자와 상속

0

이것이 C#이다

목록 보기
12/26

7.7 접근한정자로 공개 수준 결정하기

객체지향 프로그래밍에서는 선풍기의 버튼처럼 클래스의 사용자에게 필요한 최소한의 기능만 노출하고 내부를 감출것을 요구합니다. 이것을 은닉성이라고 합니다.

참고로 객체지향 프로그래밍 3대 특성은 은닉성,상속성,다형성입니다.


접근 한정자는 감추고 싶은 것은 감추고, 보여주고 싶은 것은 보여줄수있도록 코드를 수식하며 필드,메소드를 비롯하여 프로퍼티 등 모든 요소에 대해 사용할 수 있습니다.

internal 접근자가 지정된 변수, 형식은 선언된 어셈블리 내에서만 접근이 가능합니다.

이게 무슨말이냐면, 'TestLib' 이란 프로젝트를 만들고 internal 로 선언한 변수들이나 형식은 TestLib 프로젝트 내에서만 접근이 가능하고, 외부에선 접근이 불가능하게 됩니다.(어셈블리 = 프로젝트)

7.8 상속으로 코드 재활용하기

클래스는 다른 클래스로 부터 유산을 물려받을수있습니다.
클래스의 유산은 필드나 메소드,프로퍼티 같은 멤버를 뜻합니다. 객체지향에서 물려받은 클래스를 자식클래스 또는 파생클래스라 부르고, 물려주는 클래스를 부모클래스,기반클래스 라고 합니다.

class Base
{
	public void BaseMethod()
    {
    	Console.WriteLine("BaseMethod");
    }
}

//Derived는 Base를 상속했기 때문에 BaseMethod를 가집니다.
class Derived : Base
{
	//...
}

이번에는 파생클래스의 객체 생성 과정에 대해 이야기 하려합니다. 파생클래스는 자신만의 고유한 멤버 외에도 기반 클래스로부터 물려받은 멤버를 갖고 있습니다. 이것은 파생클래스가 기반 클래스 위에 새로운 멤버를 "얹어" 만든 것이기 때문입니다.

이 사실은 파생 클래스의 생성 과정을 통해서도 알 수 있습니다. 파생클래스는 객체를 생성할때 내부적으로 (기반클래스 -> 파생클래스) 순서로 생성자를 호출하고,

객체가 소멸될 때는 반대의 순서로 (파생클래스 -> 기반클래스) 순서로 종료자를 호출합니다.

7.8.1 base 키워드

만약 기반 클래스의 생성자가 매개변수를 입력받도록 선언되어 있다면 파생클래스의 인스턴스를 생성할때 어떻게 매개변수를 전달해줄 수 있을까요?
이럴 때는 base 키워드를 사용하면 됩니다. this 키워드가
자기 자신을 가리켰다면 base는 기반클래스를 가리킵니다.

class Base
{
	public void BaseMethod()
    {}
}

class Derived : Base
{
	public void DerivedMethod()
    {
    	//Base를 사용해서 자신이 상속받은 BaseMethod가 아닌 기반클래스의 메소드에 접근할 수 있습니다
    	base.BaseMethod();
    }
}

7.8.2 base 생성자 : base()

this()처럼 base()는 기반클래스의 생성자입니다. base()를 활용하면 base()에 매개변수를 넘겨 호출할 수 있습니다.

class Base
{
	protected string Name;
	public void Base(string Name)
    {
    	this.Name = Name;
    }
}

class Derived : Base
{
	public void Derived(string Name) : base(Name)
    {
    	Console.WriteLine{$"{this.Name}.Derived"}
    }
}

7.8.3 sealed 키워드로 상속 봉인하기

sealed는 작성자의 의도하지 않은 상속이나 파생클래스의 구현을 막기위한 한정자입니다.

sealed class Base
{
	//....
}

class Derived : Base // 컴파일 에러 출력!
{...}

7.9 기반 클래스와 파생클래스 사이의 형식 변환

기반 클래스와 파생 클래스 사이에는 족보를 오르내리는 형식 변환이 가능하며, 파생 클래스의 인스턴스는 기반 클래스의 인스턴스로서도 사용할 수 습니다.

mammal mammal = new Mammal();
mammal.Nurse();

//기반클래스에 파생클래스 연결
mammal = new Dog();
mammal.Nurse();

Dog dog = (Dog)mammal;
dog.Nurse();
dog.Bark();

mammal = new Cat();
mammal.Nurse();

Cat cat = (Cat)mammal;
cat.Nurse();
cat.Meow();

이런한 형식 변환은 코드의 생산성을 높줍니다. 예를 들어, 포유류 클래스의 파생클래스가 300개이상이라고 할때 형식 변환이 없다면 이 파생 포유류를 씼기는 Wash클래스를 오버로딩해야합니다.

class Zookeeper
{
	//각 포유류들을 오버로딩한 메소드들이 필요함
	public void Wash (Dog dog) {...}
    public void Wash (Cat cat) {...}
    public void Wash (Elephant elephant) {...}
    public void Wash (Lion lion) {...}
}

하지만 300개의 동물 클래스가 모두 포류류 클래스를 상속받았다면 다음과 같이 딱 하나의 Wash()메소드만 준비하면 300개의 동물 클래스에 사용할 수 있습니다.

class Zppkeeper
{
	public void Wash (Mammal mammal) {...}
}

7.9.1 is와 as

한편, C#은 형식 변환을 위해 멋진 연산자 두개를 제공합니다. is와 as입니다.
결론부터 말하자면 부모 클래스를 자식 클래스에 대입하는 경우가 발생하고, 그러한 행위를 도와주는 것이 as, is 연산자다.
is와 as의 구체적인 필요성

is - 객체가 해당 형식에 해당하는지 검사하여 그 결과를 bool로 반환합니다.
as - 형식변환 연산자와 같은 역할을 합니다. 다만 형식 변환 연산자가 실패하는 경우 예외를 던지는 반면에 as연산자는 객체 참조를 null로 만든다는것이 다릅니다.

//is 사용
Mammal mammal = new Dog();
Dog dog;


if(mammal is Dog)
{
	//mammal 객체가 Dog형식임을 확인 했으므로 안전하게 형식 변환이 이루어집니다.
	dog = (Dog)mammal;		
    dog.Bark();
}
//as 사용
Mammal mammal = new Cat();

//
Cat cat = mammal as Cat;	

//mammal2가 Cat형식 변환에 실패했다면 cat은 null이 됩니다. 
//하지만 이 코드에서 mammal2는 Cat형식에 해당하므로 안전하게 형식 변환이 이루어집니다.
if(cat != null) 
{
	cat.Meow();
}

일반적으로 (Dog),(Mammal)같은 꼴로 수행하는 형식변환대신 as연산자를 사용하는 쪽을 권장합니다. 형식변환에 실패 하더라도 예외가 일어나 갑자기 코드의 실행이 점프하는 일이 없으므로 코드를 관리하기 수월하기 때문입니다. (단,as 연산자는 참조형식에 대해서만 사용이 가능하므로 값 형식의 객체는 기존의 형식 변환 연산자를 사용해야 합니다.)

업케스팅 질문과 저자의 답변(추상적인 이야기)
업케스팅 질문과 저자의 답변(구현적인 이야기)

0개의 댓글