C# 객체지향 문법 [클래스, 필드, 메서드]
'책'이라는 개념을 보면, 어떤 공통적인 특징을 만족해야 '책'이라고 부를 수 있는가?
책에는 '제목'이 있다. 이와 마찬가지로 'ISBN 식별자', '내용', '저자', '페이지 수'가 책에 포함될 수 있다.
이러한 특징을 C#의 자료형으로 나타내는 것이 가능하다.
책 | 제목 | string |
ISBN 식별자 | decimal | |
내용 | string | |
저자 | string | |
페이지 수 | int |
특정 사물의 특징 중에는 자료형으로 표현할 수 있는 여러 가지 값이 존재하고, 객체지향에서는 이를 속성(attribute)라고 한다.
사물의 특징에는 속성만 있는 것은 아니다. 인간이라는 범주에 속하는 영희라는 객체가 책과 상호작용할 때 대표적으로 '책을 펼치고', '책을 덮는다'는 행위(behavior)를 한다.
이러한 행위까지 포함한다면 책은 다음과 같은 특징으로 정의할 수 있다.
책 | 속성 | 제목 | string |
ISBN 식별자 | decimal | ||
내용 | string | ||
저자 | string | ||
페이지 수 | int | ||
행위 | 펼친다 | ||
덮는다 |
책 뿐만 아니라 현실 세계의 모든 것을 이런 식으로 정의가 가능하다.
이 것이 '객체지향'의 핵심 개념이다.
이 개념을 프로그래밍에 적용한 것을 객체지향 프로그래밍(Object-Oriented Programming)이라 하고, 객체지향 프로그래밍이 가능한 언어를 객체지향 프로그래밍 언어(OOP Language)라고 한다.
클래스
'책'이라는 개념은 일종의 '틀(frame)'과 같고 이 틀을 기반으로 실체화된 '책'을 객체 또는 인스턴스(instance)라고 한다.
'책'에 해당하는 틀을 C#에서는 타입(Type)이라고 한다.
ex) short는 -32,768 ~ 32,767 범위의 값을 가질 수 있는 타입(틀)의 이름이다.
변수를 선언하는 구문을 객체지향 관점에서 이해하면 다음과 같다.
기본 타입 외에 별도로 객체지향 개념을 받아들여 새롭게 타입을 정의해야 하는 이유
'기본 자료형'에 속하는 타입만으로는 현실 세계를 모델링하기 역부족이기 때문이다.
기본 자료형의 태생 자체가 사람이 아닌 컴퓨터의 입장에서 메모리의 바이트 수에 따라 나눠진 형식에 불과하므로 표현의 한계가 있는 것은 당연한 결과이다.
객체지향 프로그래밍 언어는 기본 타입 외에 개발자가 원하는 모든 객체의 타입을 새롭게 정의해서 사용할 수 있게 한다.
C#에서 타입을 정의하는 방법은 class 예약어가 있다
class 클래스_명
{
//속성 정의;
//행위 정의;
}
//클래스명은 식별자이기 때문에 사용자가 임의로 정하는 것이 가능하다.
//내부에는 해당 타입이 갖는 속성 및 행위를 정의한다.
일례로 행위를 제외하고 속성만을 포함한 Book 타입은 다음과 같이 정의가 가능하다.
class Book
{
string Title;
decimal ISBN13;
string Contents;
string Author;
int PageCount;
}
클래스로 정의된 타입은 string처럼 모두 '참조형'으로 분류되므로 Book 타입을 사용하려면 우선 new 연산자로 메모리 할당을 해야 한다.
class Book뿐만 아니라 class Program 정의도 보면 C# 프로그램은 이처럼 모든 것이 타입으로 정의되어 있다는 특징이 있다.
필드
Book 클래스의 정의로 속성만 포함하고 있는데, 이렇게 정의된 속성을 C#에서는 필드(field)라고 한다.
객체에서 포함된 빌드를 프로그램에서 사용할 때는 다음과 같은 구문을 따른다.
필드에 값을 대입
객체.필드명 = 필드의_타입과_일치하는_표현식;
필드로부터 값을 가져옴
필드의_타입과_일치하는_변수 = 객체.필드명;
//객체에 속한 필드를 식별하기 위해 점(.) 연산자를 사용한다.
책의 인스턴스를 만들려면 각 필드에 값을 채워넣어야 한다.
Book gulliver = new Book();
gulliver.Author = "Jonathan Switft";
gulliver.ISBN13 = 9788983920775m;
gulliver.Title = "걸리버 여행기";
gulliver.Contents = "...";
gulliver.PageCount = 384;
값을 가져오려면 해당 필드의 타입과 동일한 타입으로 받아야 한다.
string author = gulliver.Author;
decimal isbn13 = gulliver.ISBN13;
string title = gulliver.Title;
string contents = gulliver.Contents;
int pageCount = gulliver.PageCount;
이대로 코드를 작성하면 C# 컴파일러가 오류를 발생시킨다.
- 기본적으로 객체의 필드는 외부(ex: class Program)에서 접근할 수 없기 때문이다.
- 접근하려면 명시적으로 public이라는 예약어를 사용해 클래스의 필드를 정의해야 한다.
class Book
{
public string Title;
public decimal ISBN13;
public string Contents;
public string Author;
public int pageCount;
}
간단한 정리
필드는 객체에 속한 변수이다.
메서드 내부에서 정의된 지역 변수(local varialble)와 구분하는 의미에서 멤버 변수(member variable)라고도 한다.
메서드
타입에는 속성과 행위를 정의한다.
속성은 C# 코드에서 필드로 표현되고, 행위는 메서드9method)로 표현된다.
메서드라는 말은 보통 객체지향 프로그래밍 언어에서 사용된다. C 같은 언어에서는 함수(function)라고 부르거나 서브루틴(subroutine), 프로시저(procedure)라고도 한다.
개념적으로 C#에서 메서드를 정의하는 방법은 함수를 정의하는 것과 비슷하다.
-메서드가 값을 반환하는 경우
반환타입 메서드명([타입명] [매개변수명], ...)
{
//코드 : 메서드의 본문(body)
return [반환타입에 해당하는 표현식];
}
-메서드가 값을 반환하지 않는 경우
void 메서드명([타입명] [매개변수명], ...)
{
//코드: 메서드의 본문(body)
}
//클래스를 기반으로 하는 C# 언어에서는 클래스 밖에서 메서드를 정의할 수 없다.
//메서드는 값을 반환하는 것과 그렇지 않은 유형으로 나뉜다.
//값을 반환하지 않는 메서드는 특별히 반환 타입으로 void라는 예약어를 명시하고, 메서드의 본문에는
//return 문이 없어도 된다.
//메서드는 외부에서 값을 전달받을 수 있으며 이 값을 코드에서 식별하기 위해
//매개변수(parameter)를 함께 명시할 수 있다.
//매개변수명은 메서드의 본문 내에서 일반적인 변수처럼 사용할 수 있다.
//전달할 값이 없다면 생략할 수 있다.
//메서드명과 매개변수명은 각각 식별자에 해당하므로 사용자가 임의의 이름을 지정할 수 있다.
수학의 함수와 C# 클래스의 메서드를 비교한 차이점
- C#은 값을 반환하지 않는 함수를 정의할 수 있다.
- C#은 전달하는 값과 반환받는 값의 자료형을 명시해야 한다.
메서드 역시 클래스에 필드를 선언할 때처럼 public이라는 한정자를 명시했다. 이는 필드를 사용할 때와 마찬가지로 public을 지정하지 않으면 기본적으로 클래스 안에 정의되는 모든 메서드는 외부(class Program)에서 사용할 수 없다.
클래스에서 제공하는 메서드를 호출할 때도 필드에서 점(.)을 찍고 해당 클래스에서 제공하는 메서드명을 사용하면 된다.
메서드에 전달하는 값과 반환받는 값의 관계 정의
메서드를 호출하는 측에서 전달하는 값(ex:5)은 메서드의 인자(argument)라고 한다. 메서드 내에서 는 전달된 인자 값을 매개변수(ex: int x)에 대응시켜 사용할 수 있다.
위의 코드에서는 5가 x로 전달돼 내부에서 5*5 연산을 했으므로 25라는 값을 반환한다.
C# 언어에서는 메서드에서 반환되는 값의 범위를 개발자가 미리 예상할 수 있어야 하며, 그 범위를 포함할 수 있는 타입(ex:int)을 지정해야 한다.
- 일반적으로 인자와 매개변수의 의미를 정확히 구분지어 부르지 않을 때도 있다. 가령인자를 매개변수라고 부르기도 하고, 매개변수를 인자라고 부르기도 한다.
GetValue와 Output 메서드를 호출하는 코드는 낮설게 느껴질 수 있다.
m.GetAreaOfSquare 메서드를 호출하고 인자로 m.GetValue 메서드 호출을 전달했는데, 사실 이것을 수학의 함수를 사용하는 방법을 떠올리면 당연한 표현이다.
함수 f와 g가 다음과 같이 정의된 경우
f(x) = x * x
g(x) = x - 5
//합성 함수는 g(f(x))와 같이 표현할 수 있다. 이때 x의 값이 5이면 합성 함수의 반환값은 20이 된다.
C#에서도 타입만 일치한다면 어떤 표현식이든ㄴ 메서드의 인자로 전달할 수 있다.
class Book
{
public string Title;
public decimal ISBN13;
public string Contents;
public string Author;
public int PageCount;
public void Open()
{
Console.WriteLine("Book is opened");
}
public void Close()
{
Console.WriteLine("Book is closed");
}
}
메서드 내부에서 사용되는 return 문은 값을 반환하는 목적 말고도 점프 구문의 역할도 수행한다.
public void WriteIf(bool output, string txt)
{
if(output == false)
{
return;
}
Console.WriteLine(txt);
}
중복 코드 제거
전통적으로 함수는 코드를 재사용하기 쉽게 만들어주는 역할이다.
함수를 사용하지 않아 동일한 코드가 중복되는 경우 요구 조건이 바뀌었다고 가정하면, 개발자가 중복된 코드를 모두 변경해야 하는데, 그 과정에서 특정 조건을 놓치면 프로그램이 오동작할 수 있다.
중복된 코드는 향후 유지보수를 어렵게 만든다는 심각한 단점이 있다.
이 현상은 메서드를 사용하면 문제를 개선할 수 있다.
namespace ConsoleApp2;
class Mathematics
{
public void PrintIfEven(int value)
{
if (value % 2 == 0)
{
Console.WriteLine(value);
}
}
}
class Program
{
static void Main(string[] args)
{
Mathematics m = new Mathematics();
int x = 5;
m.PrintIfEven(x);
x = 10;
m.PrintIfEven(x);
}
}
관리해야 할 코드가 한곳에 모이고, 이를 재사용할 수 있기 때문에 좀 더 간결하게 프로그램을 만들 수 있다.
원칙은 간단하다. 한 번만 사용하면 되는 코드를 메서드로 분리해야 할지는 '선택의 문제'지만, 해당 코드가 두 번 이상 중복된다면 무조건 메서드로 분리해야 한다.
코드 추상화
메서드라고 불리기 전, 함수라고 불리던 시절에는 블랙박스(black box)라는 수식어를 붙이곤 했다.
함수라는 것은 '입력 인자'와 '출력 인자'의 용도를 알고 제대로 동작하기만 한다면 내부에 어떤 식으로 코드가 작성돼 있느냐와 상관없이 이용하는 데 전혀 불편함이 없기 때문이다.
ex) 수학의 절대값을 반환하는 함수와 두 개의 인자를 받아 그중 높은 값을 반환하는 함수를 만든다고 가정한다.
이 함수를 만든 사람은 다음과 같은 도움말을 제공하고 다른 개발자가 사용하도록 만들 수 있다.
함수 이름 | 입력 인자 타입 | 출력 인자 타입 | 설명 |
abs | int | int | 입력된 값의 절댓값을 반환한다. |
max | int | int | 2개의 입력 인자 중 큰 값을 반환한다. |
int |
이 정보를 가지고 abs와 max 함수를 올바르게 사용할 수 있다.
int absoluteValue = abs(-5); //반환값은 5
int maxValue = max(absoluteValue, 10); //반환값은 10
abs와 max 함수의 내부 구조가 어떤 식으로 구현돼 있는지에 대해 외부 개발자에게 굳이 설명할 필요가 없다.
이런 식으로 메서드는 특정 목적을 수행하는 일련의 코드를 모아서 입력 인자와 출력 인자를 정의해 추상화할 수 있다.
Console.WriteLine 메서드도 그것의 내부 구현이 어떻게 되어 있는지 모르지만, 해당 메서드 값을 전달하면 화면에 출력된다는 사실을 알고 있는데 이것이 바로 추상화의 힘이다.
정리
메서드 역시 간단하게 객체에 속한 함수라고 생각하면 된다.
이 때문에 멤버 메서드(member method)라고도 불린다.
결국 필드와 메서드는 모두 해당 타입의 멤버에 속한다. 클래스의 핵심인 필드와 메서드를 알면 class를 다음과 같은 시각으로 바라볼 수 있다.
타입(class) = 속성(field) + 행위(method)
클래스는 데이터를 속성으로, 코드를 메서드로 추상화한 개념으로 객체지향 프로그래밍 언어에서 현실 세계와 프로그래밍 세계를 잇는 다리 역할을 한다.
Reference
시작하세요! C# 12 프로그래밍 기본 문법부터 실전 예제까지