<접근 한정자>
접근 한정자는 감추고 싶은 것은 감추고, 보여주고 싶은것은 보여줄 수 있도록 코드를 수식하며
필드, 메서드를 비롯해 프로퍼티 등 모든 요소에 사용할 수 있다
public : 클래스의 내부/외부 모든곳에서 접근 가능
protected : 클래스 외부에서는 접근X, 파생클래스에서는 접근 가능
private : 클래스의 내부에서만 접근 가능, 파생클래스에서는 접근 X
internal : 같은 어셈블리에 있는 코드에서만 public으로 접근 가능
만약 어떠한 접근한정자도 수식하지 않는다면 클래스의 멤버는 무조건 private로 접근 수준이 자동 지정된다
즉 클래스 내의 멤버는 일단 감추고 나중에 공개할지를 결정하는것이 순서이다
<상속으로 코드 재활용하기>
사실 유니티를 공부하며 이미 상속에 대해서 배우긴 했지만 객체지향 프로그래밍에서는
물려받는 클래스(파생클래스 혹은 자식클래스)가 물려줄 클래스를 지정한다
class 파생클래스 : 기반클래스
{
아무 멤버를 선언하지 않아도 기반 클래스의 모든 것을 물려받아 갖게 된다
단, private으로 선언된 멤버는 예외이다
}
이렇게 파생클래스의 이름 뒤에 콜론(:)을 붙여주고 그 뒤에 상속받을 기반 클래스의 이름을 붙여주면 된다
이렇듯 파생클래스는 자신만의 고유한 멤버 외에도 기반 클래스로부터 물려받은 멤버를 가지고 있다
이것은 파생 클래스가 기반 클래스 위에 새로운 멤버를 '얹어' 만든 것이기 때문이다
<기반 클래스와 파생 클래스 사이의 형식 변환, is와 as>
예를들어 강아지와 고양이, 그리고 둘을 포함하는 포유류가 있다고 치자
Mammal mammal = new mammal();
mammal.Nurse();
Dog dog = new dog();
dog.Nurse();
dog.Bark();
Cat cat = new Cat();
cat.Nurse();
cat.Meow();
보통 우리가 알고 있던 코드는 이렇게 변형이 가능하다. 말하자면 포유류는 포유류고 개도 포유류, 고양이도 포유류라는것
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.Mewo();
이처럼 기반과 파생클래스 사이에서는 족보를 오르내리는 형식변환이 가능하고
파생클래스의 인스턴스는 기반클래스의 인스턴스로서도 사용할 수 있다
그래서 이게 무슨 의미가 있냐? 코드의 생산성이 높아진다. 예를들어
public void Wash( Dog dog) { ... }
public void Wash( Cat cat) { ... }
public void Wash( Lion lion) { ... }
public void Wash( Tiger tiger) { ... }
......
이렇게 300종이 넘는 동물들을 씻기는 Wash 메서드를 구현한다고 해보자
원래 같았으면 이걸 전부 하나하나 오버로딩 해야하지만
public void Wash(Mammal mammal) { ... }
이 동물들은 모두 Mammal 클래스로부터 상속받았기 때문에 이들은 모두 Mammal로 간주한다
그리고 이런 형식 변환을 위해 C#은 is와 as라는 연산자를 제공한다
is : 객체가 해당 형식에 해당하는지 검사하여 그 결과는 bool값으로 반환한다
as : 형신 변환 연산자 같은 역할. 다만 변환에 실패하는 경우 객체 참조를 null로 만든다
Mammal mammal = new dog();
if(mammal is Dog)
{
dog = (Dog)mammal;
dog.Bark();
}
Mammal mammal2 = new Cat();
Cat cat = mammal2 as Cat;
if(cat !=null)
{
cat.Meow();
}
솔직히 아직은 정확히 어떤 식으로 사용하는지는 모르겠다...우선은 이 정도만 알고 넘어가자
<오버라이딩과 다형성>
객체지향 프로그래밍에서 다형성은 객체가 여러 형태를 가질 수 있음을 의미한다
즉, 자신으로부터 상속받아 만들어진 파생 클래스를 통해 다형성을 실현한다는 의미이다
using System;
namespace Hellooo
{
class ArmorSuite
{
public virtual void Initialize()
{
Console.WriteLine("수트 장착");
}
}
class Ironman : ArmorSuite
{
public override void Initialize()
{
base.Initialize(); // 오버라이딩을 할때 필수로 적어줘야 하는 코드. 자동완성해준다
Console.WriteLine("더블 배럴 수트 장착");
Console.WriteLine("로켓 런처 수트 장착");
}
}
class WarMachine : ArmorSuite
{
public override void Initialize()
{
base.Initialize();
Console.WriteLine("워머신 전용 수트 장착");
Console.WriteLine("리펄서 수트 장착");
}
}
class MainApp
{
static void Main(string[] args)
{
ArmorSuite armor = new ArmorSuite();
armor.Initialize();
ArmorSuite ironman = new Ironman();
ironman.Initialize();
ArmorSuite warmachine = new WarMachine();
warmachine.Initialize();
}
}
}
우선 예시코드부터 적고 시작하겠다
가장 기본이 되는 Armorsuite 클래스에 있는 Inittioalize 메서드에서는 수트 장착의 역할을 하고 있다(기본 기능)
하지만 아이언맨과 워머신에서는 각각 본인에게 필요한 개인의 기능이 필요하며
Armorsuite에서 상속받은 기능으로는 본인의 기능을 구현할 수 없다
따라서 둘은 자신들의 개인 기능을 장착하기 위해 Initialize 메서드를 재정의해줘야 한다.
다시 말해 오버라이딩(Overriding)을 해준다고 한다
하지만 여기서 조건이 있다. 바로 오버라이딩을 할때 메서드는 virtual 키워드로 한정되어 있어야 한다는것!
그리고 위에 보이는 코드처럼 각각 Armorsuite 클래스를 상속받아 오버라이딩을 할 수 있다
여기서 base.Initialize(); 라는 코드가 보인다
말 그대로 기본 ArmorSuite 클래스에 있는 기본 수트 장착 기능도 사용하겠다는것.
만약 Ironman과 Warmachine에서 base.Initialize(); 코드를 지워버리면
그럼 이런 실행결과를 확인할 수 있다
중간중간에 존재하는 기본 '수트 장착'이 아이언맨과 워머신을 호출할땐 빠져있는것을 알 수 있다
<오버라이딩 봉인하기>
클래스를 봉인하는것처럼 메서드도 sealed 키워드를 이용해 봉인할 수 있다
물론 모든 메서드를 봉인할 수 있는건 아니고 virtual로 선언된 사장 메서드를 오버라이딩한 버전의 메서드만 가능하다
class Base
{
public virtual void SealMe()
{
}
}
class Dervide : Base
{
public sealed override void SealMe() // 이 메서드만 봉인 가능
{
}
}
class WantToOverride : Dervide
{
public override void SealMe()
{
}
}
위 코드를 보면 Dervide 클래스에서 SealMe 메서드의 오버라이드를 봉인하고
WantToOverride 클래서에서 봉인된 SealMe 메서드를 오버라이드 하려 시도하고 있다
이렇게 되면 오류로 인해 컴파일이 진행되지 않는다. 그럼 도대체 이런 봉인기능은 왜 있는것일까?
다양한 이유가 있겠지만 봉인메서드는 파생클래스의 작성자를 위한 기반클래스 작성자의 배려이다
기반클래스로부터 상속받아 메서드를 오버라이딩 했는데 오작동을 일으킨다면
파생클래스 작성자는 본인의 코드에서 오류가 있는 줄 알겠지만 그게 아니기 때문.
아무튼 virtual로 선언된 메서드를 오버라이딩한 버전만 봉인할 수 있다는것을 알아두자
왜 virtual만 가능하냐? 오버라이딩을 원치 않으면 그냥 virtual 한정자를 제거해주면 되기 때문
그렇게 하면 봉인의 의미가 없어진다
<읽기 전용 필드>
const double pi = 3.141592;
위에 보이는것은 상수이다. 상수는 const 키워드를 이용해서 선언한다
상수는 프로그램이 실행되기 전부터 값이 정해져 있으며 실행중간에 절대 값을 바꿀수 없다
하지만 변수는 가능하다. 그리고 이번에 배울 읽기전용 필드는 상수와 변수 그 중간에 위치해있다
읽기전용 필드는 말 그대로 앍가먼 가능한 필드로, 클래스나 구조체의 멤버로만 존재할 수 있으며
생성자 안에서 값을 한 번 지정하면, 그 후로는 값을 변경할 수 없는게 특징이다
using System;
namespace Hellooo
{
class Base
{
private readonly int min;
private readonly int max;
public Base(int num1, int num2)
{
min = num1;
max = num2;
}
public void ChangeMax(int newmax)
{
max = newmax;
}
}
}
선언은 readonly 키워드로 하며 생성자인 Base에서 한번 정해지면
두 번 다시 값을 바꿀 수 없기에 ChangeMax 메서드에서 max 값을 바꾸려고 하는 순간 오류가 나게 된다
'C# > 2. 메서드 & 클래스 & 인터페이스' 카테고리의 다른 글
인터페이스의 개념과 상속, 추상클래스 (0) | 2025.05.06 |
---|---|
중첩 클래스, 분할 클래스, 구조체, 튜플 (0) | 2025.05.05 |
객체지향 프로그래밍, 클래스, 생성자, 정적필드(static), this 키워드 (0) | 2025.04.01 |
메서드, 매개변수, 반환 타입, return문, 참조에 의한 전달&반환, ref/out 키워드, 메서드 오버로딩, 명명된 인수, 선택적 인수, 로컬 함수 (1) | 2025.03.21 |