객체지향 프로그래밍
은닉성 (캡슐화)
은닉성(캡슐화) 의미
감추고 싶은 것은 감추고, 보여주고 싶은 것만 보여준다.
- 클래스의 사용자에게 필요한 최소의 기능만 노출하고 내부를 감추는 것
- 예를 들어 선풍기를 생각해 보면,
- 버튼 3개(바람세기 조절)와 다이얼 2개(회전과 타이머)를 사용자에게 제공
- 선풍기 케이스 안에 회로와 배선 등은 사용자에게 감춤
- 만약, 선풍기의 회로와 배선을 사용자가 조작하도록 노출한다면 문제 발생
- 캡슐화가 잘 된 클래스
- 클래스의 이름 자체에서 제공되는 기능을 대략 파악 가능
- 외부로 제공해야 할 기능에 대해서만 노출
접근 제한자(한정자) (Access Modifier)
- 감추고 싶은 것은 감추고, 보여주고 싶은 것은 보여주도록 코드를 수식
- 클래스 안에 필드, 메소드, 프로퍼티 등 모든 요소에 사용 가능
- C#에서는 총 여섯 가지 접근 제한자 제공
접근한정자
- 객체간의 상호 작용이 중심인 OOP에서는 각 객체는 다른 객체에게 자신의 내부 사정(필드, 메소드, 프로퍼티)을 공유하지 않음
- 다른 객체에게 공유해야 하는 멤버만 접근한정자를 이용하여 공개
- 접근한정자는 멤버 (필드, 메소드 등등)를 외부에 어떤 수준으로 공개할지 지정
접근 제한자 사용 형식
- 어셈블리 (Assembly)
- .NET 에서는 EXE 또는 DLL 형식의 C# 파일을 어셈블리라고 함
- 이론 상 어셈블리는 하나 이상의 모듈로 구성 (모듈 하나당 한 개의 파일)
- 일반적으로 1개의 (EXE/DLL 모듈) 파일로 구성된 어셈블리가 사용됨
- 접근 제한자로 수식하지 않은 클래스의 멤버는 무조건 private 으로 자동 지정
using System;
namespace AccessModifier
{
class WaterHeater
{
protected int temperature;
public void SetTemperature(int temperature)
{ // -5 ~ 42 사이의 값만 할당하고, 그 외의 값은 예외발생
if (temperature < -5 || temperature > 42)
{
throw new Exception("Out of temperature range");
}
// temperature 필드는 클래스 내부에서 접근 가능
this.temperature = temperature;
}
internal void TurnOnWater()
{
Console.WriteLine($"Turn on water : {temperature}");
}
}
class MainApp
{
static void Main(string[] args)
{
try
{
WaterHeater heater = new WaterHeater();
heater.SetTemperature(20);
heater.TurnOnWater();
heater.SetTemperature(-2);
heater.TurnOnWater();
// 42 값보다 큰 값이 인수로 사용, 예외발생
heater.SetTemperature(50);
heater.TurnOnWater();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
}
정보 은닉 (Information Hiding)
- 외부에서 클래스의 멤버 변수에 직접 접근 불가
- 특별한 이유를 제외하고는 필드를 public 으로 선언하지 않음
- 접근이 필요할 때는 접근자(getter)/설정자(setter) 메소드 이용해 외부 접근을 관리
using System;
namespace Hiding
{
class Circle
{
double pi = 3.14;
public double GetPi()
{ // 접근자
return pi;
}
public void SetPi(double value)
{ // 설정자
if (value <= 3 || value >= 3.15)
{
Console.WriteLine("문제 발생");
}
pi = value;
}
// ...
}
class MainApp
{
static void Main(string[] args)
{
Circle o = new Circle();
o.SetPi(3.14159);
o.SetPi(3.5); // 출력: 문제 발생
double piValue = o.GetPi();
Console.WriteLine($"piValue: {piValue}");
}
}
}
프로퍼티 (Property)
- C# 에서는 접근자/설정자를 쉽게 정의하고 사용하도록 프로퍼티 문법 제공
- 설정자 set 의 암묵적 매개변수로 “value” 예약어 사용
using System;
namespace Property
{
class BirthdayInfo
{
private string name;
private DateTime birthday;
public string Name
{
get { return name; }
set { name = value; }
}
public DateTime Birthday
{
get { return birthday; }
set { birthday = value; }
}
public int Age // 읽기 전용 프로퍼티
{
get { return new DateTime(DateTime.Now.Subtract(birthday).Ticks).Year; }
}
}
class MainApp
{
static void Main(string[] args)
{
BirthdayInfo birth = new BirthdayInfo();
birth.Name = "홍길동";
birth.Birthday = new DateTime(1991, 6, 28);
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"Birth : {birth.Birthday.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
}
}
}
자동구현 프로퍼티
- C# 3.0부터 단순히 필드를 읽고 쓰기만 할 때 자동구현 프로퍼티 사용 가능
- C# 7.0 부터는 자동구현 프로퍼티 선언과 동시에 초기화 수행 가능
using System;
namespace AutoImplementedProperty
{
class BirthdayInfo
{
public string Name { get; set; } = "Unknown";
public DateTime Birthday { get; set; } = new DateTime(1, 1, 1);
public int Age // 읽기 전용 프로퍼티
{
get { return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year; }
}
}
class MainApp
{
static void Main(string[] args)
{
BirthdayInfo birth = new BirthdayInfo();
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"Birth : {birth.Birthday.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
birth.Name = "홍길동";
birth.Birthday = new DateTime(1991, 6, 28);
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"Birth : {birth.Birthday.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
}
}
}
프로퍼티와 생성자
- 객체를 생성할 때 프로퍼티를 이용해 각 필드를 초기화
- <프로퍼티 = 값> 목록에 객체의 모든 프로퍼티가 올 필요는 없음
- 초기화하고 싶은 프로퍼티만 넣어서 초기화
using System;
namespace ConstructorWithProperty
{
class BirthdayInfo
{
public string Name { get; set; } = "Unknown";
public DateTime Birthday { get; set; } = new DateTime(1, 1, 1);
public int Age // 읽기 전용 프로퍼티
{
get { return new DateTime(DateTime.Now.Subtract(Birthday).Ticks).Year; }
}
}
class MainApp
{
static void Main(string[] args)
{
BirthdayInfo birth = new BirthdayInfo()
{
Name = "홍길동",
Birthday = new DateTime(1991, 6, 28)
};
Console.WriteLine($"Name : {birth.Name}");
Console.WriteLine($"Birth : {birth.Birthday.ToShortDateString()}");
Console.WriteLine($"Age : {birth.Age}");
}
}
}
초기화 전용 자동 구현 프로퍼티
- 프로퍼티를 객체를 생성할 때 초기화 후, 중간에 변경 못 하도록 설정
- 자동 구현 프로퍼티에서 set 키워드 대신에 init 키워드 사용
using System;
namespace InitOnly
{
class Transaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
}
class MainApp
{
static void Main(string[] args)
{
Transaction tr1 = new Transaction { From = "Alice", To = "Bob", Amount = 100 };
Transaction tr2 = new Transaction { From = "Bob", To = "Charlie", Amount = 50 };
Transaction tr3 = new Transaction { From = "Charlie", To = "Alice", Amount = 50 };
//tr1.From = "Charlie"; 값 할당 시 컴파일 에러 발생
Console.WriteLine($"{tr1.From, -10} -> {tr1.To,-10} : {tr1.Amount,-10}");
Console.WriteLine($"{tr2.From,-10} -> {tr2.To,-10} : {tr2.Amount,-10}");
Console.WriteLine($"{tr3.From,-10} -> {tr3.To,-10} : {tr3.Amount,-10}");
}
}
}
레코드 형식의 불변 객체
- 클래스는 참조 형식이기 때문에 객체 사이의 필드 복사, 비교, 출력 등에 있어 추가적 구현 필요
- record 키워드를 사용하는 레코드 형식 사용
- 값을 담는 용도의 클래스의 역할
- 컴파일 시 복사, 비교, 출력 등의 메서드 자동추가
- 따라서, record 형식은 class + “기본 생성 코드”
- 레코드 형식의 불변 객체
using System;
namespace Record
{
record RTransaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
}
class MainApp
{
static void Main(string[] args)
{
RTransaction tr1 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
RTransaction tr2 = new RTransaction { From = "Bob", To = "Charlie", Amount = 50 };
Console.WriteLine($"{tr1.From,-10} -> {tr1.To,-10} : {tr1.Amount,-10}");
Console.WriteLine($"{tr2.From,-10} -> {tr2.To,-10} : {tr2.Amount,-10}");
}
}
}
with를 이용한 레코드 복사
- with 키워드를 사용해 두 record 객체 사이의 깊은 복사를 수행
- 깊은 복사와 동시에 일부 필드 값 변경 가능
using System;
namespace WithExp
{
record RTransaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
}
class MainAoo
{
static void Main(string[] args)
{
RTransaction tr1 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
RTransaction tr2 = tr1 with { To = "Charlie" }; // with 사용을 통한 깊은 복사
RTransaction tr3 = tr1 with { From = "Charlie", Amount = 50 }; // 일부 필드 값 수정
Console.WriteLine($"{tr1.From,-10} -> {tr1.To,-10} : {tr1.Amount,-10}");
Console.WriteLine($"{tr2.From,-10} -> {tr2.To,-10} : {tr2.Amount,-10}");
Console.WriteLine($"{tr3.From,-10} -> {tr3.To,-10} : {tr3.Amount,-10}");
}
}
}
레코드 객체 비교하기
- 클래스에서는 자신과 다른 객체를 비교하기 위해 Equals() 메소드 사용
- 필드들을 일일이 비교하기 위해 Equals() 메소드 재정의 (override) 필요
- 재정의 없이 Equals() 메소드 사용 시 객체의 참조 주소 값만 비교
- 레코드 객체는 컴파일러가 Equals() 메소드를 자동으로 구현
using System;
namespace RecordComp
{
class CTransaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
public override bool Equals(object obj)
{
CTransaction target = (CTransaction)obj;
if (this.From == target.From && this.To == target.To && this.Amount == target.Amount)
return true;
else
return false;
}
}
record RTransaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
}
class MainApp
{
static void Main(string[] args)
{
CTransaction trA = new CTransaction { From = "Alice", To = "Bob", Amount = 100 };
CTransaction trB = new CTransaction { From = "Alice", To = "Bob", Amount = 100 };
Console.WriteLine(trA.Equals(trB));
RTransaction tr1 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
RTransaction tr2 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
Console.WriteLine(tr1.Equals(tr2));
}
}
}
using System;
namespace RecordComp
{
class CTransaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
}
record RTransaction
{
public string From { get; init; }
public string To { get; init; }
public int Amount { get; init; }
}
class MainApp
{
static void Main(string[] args)
{
CTransaction trA = new CTransaction { From = "Alice", To = "Bob", Amount = 100 };
CTransaction trB = new CTransaction { From = "Alice", To = "Bob", Amount = 100 };
Console.WriteLine(trA.Equals(trB));
RTransaction tr1 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
RTransaction tr2 = new RTransaction { From = "Alice", To = "Bob", Amount = 100 };
Console.WriteLine(tr1.Equals(tr2));
}
}
}
무명 형식
- 형식의 선언과 동시에 객체를 할당
- 객체를 만들고 다시는 그 형식을 사용하지 않을 때 사용
- 무명 형식의 프로퍼티에 할당된 값은 변경 불가 (읽기만 가능)
using System;
namespace AnonymousType
{
class MainApp
{
static void Main(string[] args)
{
var a = new { Name = "홍길동", Age = 123 };
Console.WriteLine($"Name: {a.Name}, Age: {a.Age}");
var b = new { Subject = "수학", Scores = new int[] { 90, 80, 70, 60 } };
Console.Write($"Subject: {b.Subject}, Scores: ");
foreach (var score in b.Scores)
Console.Write($"{score} ");
Console.WriteLine();
}
}
}
'전공 > C# 프로그래밍' 카테고리의 다른 글
7강. 객체지향 프로그래밍과 클래스 (4)다형성 (0) | 2023.04.18 |
---|---|
7강. 객체지향 프로그래밍과 클래스 (3)상속성 (0) | 2023.04.17 |
7강. 객체 지향 프로그래밍과 클래스 (1)클래스 (0) | 2023.04.17 |
6강. 메소드 (Method) (0) | 2023.04.17 |
5강. 코드 흐름 제어 (0) | 2023.04.16 |
댓글