학생 시절 접근제한자를 사용하는 이유는 대략적으로 알겠는데 get, set 즉, 프로퍼티를 왜 사용하는지 몰랐던 적이 있습니다. 이유를 모를 경우 get set을 사용 안 하게 되고, 비효율적인 프로그래밍으로 이어지게 됩니다. get set 사용하면 메서드를 최소화 하여 만들 수 있습니다. 이번 시간에는 접근제한자와 프로퍼티의 차이와 사용이유, 장점, 단점 등을 살펴보고 제대로 사용할 수 있도록 가이드 라인을 제공하겠습니다.
목차
[프로퍼티 사용법과 예시 코드]
[개념]
- “get”은 값을 반환하는 메서드입니다.
- “set”은 값을 설정하는 메서드입니다.
- 이러한 접근자를 사용하여 외부에서 클래스의 멤버 변수에 직접 접근하지 않고, 값의 유효성을 확인하거나 다른 로직을 수행할 수 있습니다.
[사용법]
- 클래스 내부에서 정의되며, 필드와 유사한 방식으로 선언됩니다.
- 각 프로퍼티는 get 접근자와 set 접근자를 가질 수 있습니다.
- 필요에 따라 set 접근자를 생략하여 읽기 전용 프로퍼티를 만들 수 있습니다.
[예시코드]
using System;
class Rectangle
{
// Private fields
private int width;
private int height;
// Width property with get and set accessors
public int Width
{
get { return width; } // Getter
set
{
if (value > 0)
width = value; // Setter
else
Console.WriteLine("Width should be a positive number.");
}
}
// Height property with get and set accessors
public int Height
{
get { return height; } // Getter
set
{
if (value > 0)
height = value; // Setter
else
Console.WriteLine("Height should be a positive number.");
}
}
// Method to calculate area
public int CalculateArea()
{
return Width * Height;
}
}
class Program
{
static void Main(string[] args)
{
Rectangle rect = new Rectangle();
// Setting width and height using properties
rect.Width = 5;
rect.Height = 10;
// Getting width and height using properties
Console.WriteLine("Width: " + rect.Width); // Output: Width: 5
Console.WriteLine("Height: " + rect.Height); // Output: Height: 10
// Calculating area
Console.WriteLine("Area: " + rect.CalculateArea()); // Output: Area: 50
}
}
위의 코드에서는 Rectangle
클래스를 정의하고, Width
와 Height
프로퍼티를 사용하여 너비와 높이를 설정하고 가져오는 방법을 보여줍니다. set 접근자에서는 값의 유효성을 검사하여 음수 값이 들어오지 않도록 처리합니다.
[프로퍼티의 장점과 단점]
[장점]
- 캡슐화와 코드 유지 보수성: 클래스의 내부 상태를 캡슐화하여 외부에서 직접적으로 접근하지 않도록 합니다. 게임에서는 플레이어의 능력치, 아이템의 속성 등과 같이 중요한 데이터를 캡슐화하여 보호할 수 있습니다. 이를 통해 코드의 유지 보수성이 향상되고 예기치 않은 버그를 방지할 수 있습니다.
- 값의 유효성 검사: set 접근자를 사용하여 값의 유효성을 검사할 수 있습니다. 예를 들어, 플레이어의 체력이 음수가 되지 않도록 제한할 수 있습니다. 이는 게임의 논리를 유지하고 안전한 상태를 유지하는 데 도움이 됩니다.
- 간편한 접근과 변경: 사용하면 단순한 필드처럼 보이지만, 실제로는 메소드 호출과 같은 동작을 수행할 수 있습니다. 이를 통해 데이터에 접근하거나 값을 변경하는 것이 더 간편해집니다. 예를 들어, 게임에서 플레이어의 경험치를 얻거나 설정할 때, 일반적인 필드보다는 프로퍼티를 사용하는 것이 편리합니다.
[장점의 예시코드]
using System;
class Player
{
// 캡슐화와 코드 유지 보수성을 위한 프로퍼티
private int _health;
public int Health
{
get { return _health; }
set
{
if (value > 0)
_health = value;
else
Console.WriteLine("Health should be a positive number.");
}
}
// 값의 유효성 검사를 통한 안전한 데이터 설정
private int _experience;
public int Experience
{
get { return _experience; }
set
{
if (value >= 0)
_experience = value;
else
Console.WriteLine("Experience cannot be negative.");
}
}
// 간편한 접근과 변경을 위한 프로퍼티
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
// 생성자
public Player(string name, int health, int experience)
{
Name = name;
Health = health;
Experience = experience;
}
// 경험치 증가 메소드
public void GainExperience(int amount)
{
Experience += amount;
Console.WriteLine($"{Name} gained {amount} experience.");
}
// 플레이어 정보 출력 메소드
public void PrintPlayerInfo()
{
Console.WriteLine($"Name: {Name}, Health: {Health}, Experience: {Experience}");
}
}
class Program
{
static void Main(string[] args)
{
// 플레이어 생성
Player player1 = new Player("Hero", 100, 0);
// 플레이어 정보 출력
player1.PrintPlayerInfo(); // Output: Name: Hero, Health: 100, Experience: 0
// 플레이어의 경험치 증가
player1.GainExperience(50); // Output: Hero gained 50 experience.
// 플레이어 정보 다시 출력
player1.PrintPlayerInfo(); // Output: Name: Hero, Health: 100, Experience: 50
// 잘못된 값을 설정하여 유효성 검사
player1.Health = -50; // Output: Health should be a positive number.
player1.Experience = -20; // Output: Experience cannot be negative.
// 플레이어 정보 다시 출력
player1.PrintPlayerInfo(); // Output: Name: Hero, Health: 100, Experience: 50
}
}
이렇게 get set 프로퍼티를 사용할 경우 3가지 장점을 취하여 확장성과 유지보수성이 올라갑니다. 예를들어서 Health를 set할 때마다 hp체력바를 보여주고 싶다면 내부에서 이벤트를 발생시키거나, UI를 설정해주는 코드만 있으면 손쉽게 가능합니다. 하지만 그냥 변수라면 사용한 부분을 모두 찾아가서 고쳐줘야 합니다. 이렇게 프로퍼티는 메서드의 장점을 취하면서도 메서드보다 훨씬 사용이 간편합니다.
[단점]
- 오버헤드: 간단한 필드에 비해 성능 오버헤드가 발생할 수 있습니다. 이는 프로퍼티가 실제로는 메소드로 컴파일되기 때문에 발생합니다. 따라서 게임에서 매우 빈번하게 호출되는 곳에 프로퍼티를 사용하면 성능에 영향을 줄 수 있습니다.
- 복잡성 증가: 코드의 가독성이 향상되지만, 때로는 불필요한 복잡성을 초래할 수 있습니다. 특히, 복잡한 로직이나 계산이 필요한 경우에는 메소드를 사용하는 것이 더 적합할 수 있습니다.
[단점의 예시코드]
- 오버헤드 예시
using System;
class Player
{
private int _health;
// 프로퍼티를 사용하여 값을 설정
public int Health
{
get { return _health; }
set { _health = value; }
}
// 프로퍼티를 사용하지 않고 필드에 직접 접근하여 값을 설정
public void SetHealth(int health)
{
_health = health;
}
}
class Program
{
static void Main(string[] args)
{
Player player = new Player();
// 프로퍼티를 사용하여 값 설정 (오버헤드 발생)
player.Health = 100;
// 메소드를 사용하여 값 설정 (오버헤드 없음)
player.SetHealth(100);
}
}
이 코드에서는 Player
클래스의 Health
프로퍼티를 설정하는 방법을 보여줍니다. 값을 설정할 때 해당 프로퍼티의 set 접근자가 호출되므로 약간의 성능 오버헤드가 발생합니다. 반면에 메소드를 사용하여 값을 설정하면 직접 필드에 접근하므로 오버헤드가 없습니다.
- 복잡성 증가
using System;
class Player
{
private int _level;
// 복잡한 로직을 갖는 프로퍼티
public int Level
{
get
{
// 복잡한 계산 로직
return _level * 2;
}
set
{
// 복잡한 로직
_level = value / 2;
}
}
// 복잡한 로직을 갖는 메소드
public void SetLevel(int level)
{
// 복잡한 계산 로직
_level = level / 2;
}
}
class Program
{
static void Main(string[] args)
{
Player player = new Player();
// 복잡한 로직을 갖는 프로퍼티 사용
player.Level = 10;
// 복잡한 로직을 갖는 메소드 사용
player.SetLevel(10);
}
}
이 코드에서는 Player
클래스의 Level
프로퍼티와 SetLevel
메소드를 비교합니다. Level
프로퍼티는 get 및 set 접근자 내에 복잡한 계산 로직을 포함하고 있습니다. 이는 가독성을 떨어뜨리고 코드를 이해하기 어렵게 합니다. 반면에 SetLevel
메소드는 복잡한 로직을 가지지만, 메소드 이름만으로도 그 기능을 파악하기 쉽습니다.
[프로퍼티를 왜 쓸까?]
- 캡슐화와 정보 은닉: 클래스의 내부 데이터를 캡슐화하여 외부에서 직접적으로 접근하는 것을 방지할 수 있습니다. 이는 객체 지향 프로그래밍의 기본 원칙 중 하나인 정보 은닉을 구현하는데 도움이 됩니다. 객체의 내부 상태를 숨기고 외부에서 접근할 때 제한을 두어 데이터의 무결성을 보호할 수 있습니다.
- 값의 유효성 검사: set 접근자를 사용하여 값을 설정할 때 유효성 검사를 수행할 수 있습니다. 예를 들어, 음수 값이 들어오지 않도록 하거나 범위를 제한할 수 있습니다. 이를 통해 데이터의 무결성을 유지하고 예기치 않은 상태를 방지할 수 있습니다.
- 간편한 접근 및 변경: 필드에 접근하고 값을 설정하는 것과 동일한 구문을 사용하여 코드를 간결하게 유지할 수 있습니다. 필드와 메소드 사이의 중간 지점으로 생각할 수 있으며, 필드에 직접 접근하는 것보다 안전하고 강력한 기능을 제공합니다.
- 읽기 전용 및 쓰기 전용 프로퍼티: 프로퍼티를 읽기 전용 또는 쓰기 전용으로 만들 수 있습니다. 이는 객체의 상태를 변경하지 않고 읽기만 할 수 있는 경우에 유용하며, 읽기 전용 프로퍼티를 통해 변경 불가능한 객체를 생성할 수 있습니다.
[접근제한자의 개념과 사용법 및 예시코드]
public:
- 가장 널리 사용되는 접근 제한자입니다.
- 해당 클래스, 멤버 변수 또는 메소드는 어디서든 접근할 수 있습니다.
2. private:
- 해당 클래스 내에서만 접근할 수 있습니다.
- 외부에서는 접근할 수 없으며, 클래스 외부에서는 해당 멤버에 접근할 수 없습니다.
3. protected:
- 해당 클래스 내부와 파생 클래스에서만 접근할 수 있습니다.
- 파생 클래스에서는 부모 클래스의 protected 멤버에 접근할 수 있지만, 외부에서는 접근할 수 없습니다.
4. internal:
- 같은 어셈블리(assembly) 내에서만 접근할 수 있습니다.
- 다른 어셈블리에서는 접근할 수 없습니다.
5. protected internal:
- 같은 어셈블리 내에서는 public으로 동작하고, 파생 클래스에서도 접근할 수 있습니다.
- 다른 어셈블리에서는 protected 멤버로 동작합니다.
[사용법 및 예시코드]
using System;
// 예시 클래스 정의
public class ExampleClass
{
// public 접근 제한자를 갖는 멤버 변수
public int publicVar;
// private 접근 제한자를 갖는 멤버 변수
private int privateVar;
// protected 접근 제한자를 갖는 멤버 변수
protected int protectedVar;
// internal 접근 제한자를 갖는 멤버 변수
internal int internalVar;
// protected internal 접근 제한자를 갖는 멤버 변수
protected internal int protectedInternalVar;
// public 접근 제한자를 갖는 메소드
public void PublicMethod()
{
Console.WriteLine("This is a public method.");
}
// private 접근 제한자를 갖는 메소드
private void PrivateMethod()
{
Console.WriteLine("This is a private method.");
}
// protected 접근 제한자를 갖는 메소드
protected void ProtectedMethod()
{
Console.WriteLine("This is a protected method.");
}
// internal 접근 제한자를 갖는 메소드
internal void InternalMethod()
{
Console.WriteLine("This is an internal method.");
}
// protected internal 접근 제한자를 갖는 메소드
protected internal void ProtectedInternalMethod()
{
Console.WriteLine("This is a protected internal method.");
}
}
// 메인 클래스
class Program
{
static void Main(string[] args)
{
ExampleClass example = new ExampleClass();
// public 멤버 변수와 메소드에 접근
example.publicVar = 10;
example.PublicMethod();
// private 멤버 변수와 메소드에 접근 불가 (컴파일 에러 발생)
// example.privateVar = 20;
// example.PrivateMethod();
// protected 멤버 변수와 메소드에 접근 불가 (컴파일 에러 발생)
// example.protectedVar = 30;
// example.ProtectedMethod();
// internal 멤버 변수와 메소드에 접근
example.internalVar = 40;
example.InternalMethod();
// protected internal 멤버 변수와 메소드에 접근
example.protectedInternalVar = 50;
example.ProtectedInternalMethod();
}
}
[접근제한자의 특징]
- 접근 범위 제어: 해당 멤버 또는 클래스가 어디서 접근 가능한지를 제어합니다. 이를 통해 필요한 정보만을 외부에 노출시키고, 불필요한 정보는 감추어 캡슐화를 유지할 수 있습니다.
- 정보 은닉: private을 사용하면 클래스 외부에서 해당 멤버에 접근할 수 없습니다. 이는 클래스의 내부 구현을 숨기고 객체의 상태를 보호하는 데 도움이 됩니다. 따라서 객체의 무결성을 보장하고 예기치 않은 변경을 방지할 수 있습니다.
- 상속과 다형성: protected를 사용하면 파생 클래스에서 부모 클래스의 멤버에 접근할 수 있습니다. 이는 상속 관계에서 부모 클래스의 구현을 재사용하거나 확장하는 데 도움이 됩니다. 또한 다형성을 통해 여러 객체를 동일한 인터페이스로 처리할 때 유용합니다.
- 클래스 간의 관계 제어: internal은 같은 어셈블리 내에서만 접근 가능하도록 제한합니다. 이는 여러 클래스 간의 관계를 제어하고 외부에서의 불필요한 의존성을 줄이는 데 도움이 됩니다.
- 코드 가독성과 유지 보수성: public은 다른 클래스나 모듈에서 해당 멤버에 접근할 수 있도록 허용합니다. 이는 코드의 가독성을 높이고 재사용성을 향상시키는 데 도움이 됩니다. 또한 적절한 접근 제한자를 사용하면 코드의 유지 보수성을 높일 수 있습니다.
- 애플리케이션 보안 강화: 중요한 데이터나 메소드에 대한 외부 접근을 제한함으로써 애플리케이션의 보안을 강화할 수 있습니다.
[프로퍼티와 접근제한자의 차이]
- 프로퍼티: 주로 클래스의 필드에 대한 간접적인 접근을 제공하는 데 사용됩니다. 값을 가져오거나 설정할 때 특정 로직을 수행할 수 있습니다. 주로 데이터의 은닉이 아니라 데이터에 대한 간접적인 접근을 제공하는 데 사용됩니다.
- 접근 제한자: 주로 클래스의 멤버에 대한 접근을 제어하는 데 사용됩니다. 외부에서의 접근을 제한하여 정보 은닉과 캡슐화를 구현하는 데 중요한 역할을 합니다.
[어떤 상황에 사용하면 유리할까?]
- 프로퍼티: 주로 데이터에 대한 간접적인 접근을 제공할 때 유용합니다. 데이터의 값을 가져오거나 설정할 때 특정 로직을 수행하고 싶은 경우에 사용됩니다. 예를 들어, 유효성 검사를 수행하거나 값의 변경을 추적하는 등의 작업을 할 수 있습니다.
- 접근 제한자: 주로 클래스의 멤버에 대한 접근을 제어할 때 사용됩니다. 클래스의 내부 구현을 숨기고 외부에서 직접적인 접근을 제한하여 정보 은닉과 캡슐화를 실현할 때 유용합니다.
프로퍼티는 데이터에 대한 간접적인 접근을 제공하고, 접근 제한자는 클래스의 멤버에 대한 접근을 제어하는 데 목적을 둡니다.. 즉, 프로퍼티는 내부적으로 값을 직접 접근하는 것을 막아서 코드의 유연성을 향샹 시켜주는 역할을 하고 접근제한자는 클래스 외부에서 접근하면 안되는 변수를 내부에서만 접근할 수 있게 해주는 것 입니다.
[마무리 글]
프로퍼티와 접근제한자의 차이점을 잘 이해하시고, 각각의 장점을 이해하시면 어떻게 사용해야하는 지 감이 오실 것 입니다. 또한 프로퍼티를 잘 사용하셔서 메서드와 변수의 각각 이점을 잘 취하시기 바랍니다.