추상화란 복잡한 자료, 모듈, 시스템 등으로부터 핵심적인 개념 또는 기능을 간추려 내는 것을 말합니다.
추상화는 이처럼 구체적인 사물들간의 공통점을 취하고 차이점을 버리는 일반화를 사용하거나,
중요한 부분을 강조하기 위해 불필요한 세부 사항을 제거함으로써 단순하게 만듭니다.
결국 핵심은 불필요한 코드를 제거하고 중요한 부분을 살리는 것입니다.
문법으로는 일반화 클래스(Generic class), 추상 클래스(abstract class), 추상 매소드(abstract method)가 대표적입니다.
이전 시간에 배운 상속은 다음과 같습니다.
상속성이란 하나의 클래스가 가지고 있는 데이터(값,기능)을 다른 클래스가 그대로 물려받는 것을 뜻합니다.
이때 데이터를 넘겨주는 클래스는 부모클래스, 물려받는 클래스는 자식클래스입니다.
그리고 데이터의 전달은 부모 -> 자식 (상위 -> 하위)의 단일 방향으로만 진행됩니다.
추상화를 통해 부모클래스를 만들고
상속성을 이용해 자식클래스에게 데이터를 공통적으로 전달하고
추가적인 데이터는 자식클래스에서 표현하면 코드는 매우 간결해집니다.
상속성은 코드를 매우 간결하게 만들어주는 객체지향 언어의 가장 큰 장점이라고 말 할 수 있습니다.
이번에는 상속한 클래스를 어떻게 사용해야하는지 캐스팅에 대하여 자세히 알아보도록 해보겠습니다.
이해를 위해 플레이어를 상속한 메이지와 나이트를 만들었습니다.
abstract class Player //플레이어는 추상클래스입니다.
{
public int Hp { get; set; } //체력포인트
public int Mp { get; set; } //마나포인트
public Player()
{
Hp = 150;
Mp = 60;
}
abstract public void Attack(); //추상 메소드
public void BuyItem()
{
Console.WriteLine("아이템을 구매합니다.");
}
}
class Mage : Player //플레이어를 상속한 메이지
{
public Mage() : base()
{
Mp = 120;
}
public override void Attack()
{
Console.WriteLine("마법 공격");
}
public void FireBall()
{
Console.WriteLine("스킬 사용 : 파이어볼");
}
}
class Knight : Player //플레이어를 상속한 나이트
{
public Knight() : base()
{
Hp = 250;
}
public override void Attack()
{
Console.WriteLine("힘껏 때리기");
}
public void Whirlwind()
{
Console.WriteLine("스킬 사용: 날려버리기");
}
}
현재 상속을 그림으로 보면 이렇게 됩니다. 보통 상속을 표현할때는 파생클래스가 부모클래스를 화살표로 가리키는 모습으로 그립니다.

이 상태로 메이지와 나이트의 인스턴스를 만들면 파생클래스가 부모클래스의 코드를 가지고있는 이런 그림이 됩니다.


업 캐스팅(Upcasting)은 상속 관계에서 파생 클래스(하위 클래스)의 인스턴스를 부모 클래스(상위 클래스)의 타입으로 캐스팅하는 것을 말합니다. 즉, 업 캐스팅은 기반 클래스의 참조로 파생 클래스의 인스턴스를 가리키는것을 말합니다.
업 캐스팅을 하는 문법적 방법은 3가지가 있습니다. 이 중 다이렉트 업캐스팅을 가장 자주 사용합니다.
다음은 업 캐스팅의 예입니다.
// 명시적 업캐스팅
Mage mage = new Mage();
Player player = (Player)mage;
// 묵시적 업캐스팅
Mage mage = new Mage();
Player player = mage;
//다이렉트 업캐스팅
Player player = new Mage();

변수처럼 값(데이터)를 담는것이아니라 참조하는 것입니다.
모든 메이지는 플레이어다. (O)
모든 플레이어는 메이지다. (X)
파생 클래스가 부모 클래스의 모든 멤버를 상속하기 때문에 참조가 가능한겁니다.
물론 업 캐스팅을 하여 인스턴스를 참조했다곤해도 파생 클래스의 모든 기능을 사용할 수는 없습니다.
사용할 수 있는것은 파생 클래스 안에있는 부모 클래스의 권한뿐 입니다.

예제를 들어보겠습니다.
Player player = new Mage(); // 업 캐스팅
player.플레이어에서_만든_함수() // 가능) 플레이어 클래스가 가지고있는 함수는 사용 가능
player.메이지에서_만든_함수(); // 불가능) 메이지가 가지고있는 함수는 권한의 범위를 초과합니다.

다운 캐스팅(Downcasting)은 업 캐스팅과 반대로 부모 클래스의 인스턴스를 파생 클래스의 타입으로 캐스팅하는 것을 말합니다.
다운 캐스팅은 컴파일러에게 해당 객체가 실제로 파생 클래스의 인스턴스임을 알려주는 역할을 합니다.
다운 캐스팅은 업 캐스팅을 한 상태에서 다시 내려가는 것을 의미합니다.
즉, 참조와 인스턴스를 다시 맞추는 작업입니다.
다음은 다운 캐스팅의 예입니다.
Player player = new Mage();
Mage mage = (Mage)player;
위 코드는 사실 조금 위험합니다. 실제로 인스턴스가 메이지가 들어간다면 문제가 없습니다만,
문제는 실수로 인스턴스가 나이트가 들어갔을 때 입니다.
Player player = new Knight();
Mage mage = (Mage)player;

출력
(컴파일 오류 발생)
Unhandled exception. System.InvalidCastException: Unable to cast object of type
이유는 (실제로 나이트가 들어갔음에도) 컴파일러가 컴파일 전까지 오류를 안보여주기 때문입니다.
다운 캐스팅은 런타임에 객체가 실제로 해당 파생 클래스의 인스턴스인지 확인해야 하며, 잘못된 캐스팅은 런타임 에러를 발생시킬 수 있습니다.
이런 이유로 다운 캐스팅을 안전하게 하기위해서 is와 as를 사용합니다.
// is 사용법
if(player is Mage) // ’player에 담긴 인스턴스‘가 Mage 클래스인지 확인한다.
{
Mage mage = (Mage)player; // 한번 확인을 거쳤기 때문에 안전하게 사용 가능.
}
// as 사용법
Player player = new Mage();
Mage mage = (player as Mage); // 잘못된 다운 캐스팅이라면 null을 넣어줍니다.
//업 캐스팅 후 다운 캐스팅
Player player = new Mage();
Mage mage = (player as Mage);
//<질문> 그렇다면 이건 안되는 건가?
Mage mage = new Player(); //오류가 표시된다.
왜 직접 메이지가 플레이어를 가리키는건 안되는걸까요?
추상적으로 말한다면
위 코드는 ”모든 플레이어는 메이지이다.“를 말한것이 됩니다.
메이지 입장에서 자신이 플레이어임은 알고있지만
플레이어 입장에서 자신이 메이지임은 알 수 없는것이죠
플레이어는 단순히 플레이어입니다 플레이어는 자신이 어디에 상속 될것인지 모르고 있습니다.
만약 이것이 가능하다면
메이지가 플레이어 권한밖에 쓸 수 없는데 이건 무슨 의미가 있는 코드일까요? 이럴바엔 단순히 플레이어 참조가 플레이어를 가리키는것이 훨씬 알아보기 쉬울것입니다.
결론
부모 객체를 자식 객체에 대입할 수 없다.
업캐스팅을 배우면서 "어차피 자식 클래스의 멤버에는 접근도 못하는데, 뭐하러 있는거지?" 라는 의문이 들 수도 있습니다.
이것의 해답은 "다형성(Polymorphism)"과 관련있습니다.
다형성이란 하나의 메소드나 클래스가 있을 때 이것들이 다양한 방법으로 동작하는 것을 의미합니다.
메소드는 오버로딩과 오버 라이딩을 통하여 하나의 함수이름으로 다양한 기능을 동작가능하게 만들수 있습니다.
인터페이스는 특정한 인터페이스를 구현하고 있는 클래스가 있을 때 이 클래스의 데이터 타입으로 인터페이스를 지정 할 수 있습니다. 즉, 인터페이스를 커넥터처럼 사용가능합니다.
마지막으로 클래스는 캐스팅을 통해 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있습니다.
아래는 오버 라이딩으로 구현한 Attack()을 업캐스팅 방식을 사용하여 활용한 예제입니다.
class ConsoleApp
{
public static void damage(Player player) //업캐스팅으로 사용하세요
{
player.Attack();
}
public static void Main()
{
damage(new Mage());
damage(new Knight());
}
}
출력
마법 공격
힘껏 때리기
위 코드에서 특별한점은 damage 함수 하나만 작성하여
자식 클래스를 매개변수로 넣어 사용하고 있다는 점입니다.
이게 왜 좋은 점인지 리그 오브 레전드를 통해 예시를 들어보겠습니다.
리그 오브 레전드에는 챔피언이 150정도 있습니다.
그리고 리그 오브 레전드의 모든 챔피언은 평타라는 일반 공격 수단을 가집니다.
이말은 즉, 모든 챔피언에 Autoattack() 이라는 함수가 있다고 생각해야 합니다.
그렇다면 damage() 함수를 만든다고 했을때 150개의 damage() 함수를 만들어야합니다.
예를 들면 이렇게 말이죠
public void Garen_damage(가렌 Garen) {}
public void Galio_damage(갈리오 Galio) {}
public void Gangplank_damage(갱플랭크 Gangplank) {}
public void Gragas_damage(그라가스 Gragas) {}
public void Graves_damage(그레이브즈 Graves) {}
...
...
public void Heimerdinger_damage(하이머딩거 Heimerdinger) {}
이건 함수의 수가 많아서 버그를 만들어낼 수 도 있는 나쁜 예시입니다.
따라서 캐스팅은 함수 하나로 오버라이드하여 사용할 수 있도록 코드를 정리해주는 좋은 기능입니다.
은닉성은 클래스를 사용자에게 필요한 최소의 기능만 노출하고 내부를 감추는 것입니다.
은닉성을 다루기 위하여 public, protected, private가 어느 정도의 위치까지 함수를 공개하는지 알아보도록 하겠습니다.
예제를 통해 어느 범위까지 메소드를 사용가능한지 알아보겠습니다.
👇플레이어 코드
class Player
{
public int Hp { get; set; } //체력포인트
public int Mp { get; set; } //마나포인트
public Player()
{
Hp = 150;
Mp = 60;
DebugMessage();
}
public virtual void Attack() //public을 통해 외부/내부에 모두 노출
{
Console.WriteLine("평타.");
}
protected void SetMp(int mp) //protected을 통해 부모 클래스와 자식클래스까지만 노출
{
Mp = mp;
}
private void DebugMessage() //private을 통해 내부적으로만 사용할때 쓰임
{
Console.WriteLine($"현재 Hp : {Hp}");
}
}
👇메이지 코드 (플레이어를 상속함)
class Mage : Player //플레이어를 상속한 메이지
{
public Mage() : base()
{
Mp = 120;
}
public override void Attack()
{
Console.WriteLine("마법 공격");
}
public void FireBall()
{
Console.WriteLine("스킬 사용 : 파이어볼");
Mp -= 20;
SetMp(Mp);
Console.WriteLine(Mp);
}
}
👇콘솔앱 코드
class ConsoleApp
{
public static void damage(Player player) //업캐스팅으로 사용하세요
{
player.Attack();
}
public static void Main()
{
Mage mage = new Mage();
mage.FireBall();
}
}
플레이어의 코드를 봐 주세요.
Attack()은 메이지 뿐만 아니라 콘솔앱에서도 접근해야 합니다.
따라서 해당 기능은 public으로 모두 노출하는것이 좋겠죠?
반대로 DebugMessage() 처럼 플레이어만 단독으로 사용하고 싶은 기능은
외부에 노출 시킬필요가 없습니다. 따라서 private로 설정하는것이죠.
마지막으로 SetMp()는 콘솔앱에서는 사용할 필요가 없고❌,
자식 클래스인 메이지에서만 사용👌하는 함수입니다.
따라서 protected를 사용하여 부모인 플레이어와 자식인 메이지까지만 사용할 수 있도록 설정합니다.