Kotlin을 보다가 backing field를 접하며, 이게 뭔가싶었는데 다른 언어들도 다 지원하는 property에 대한 내용이었다. 그동안 솔직히 member variable과 property에 대한 개념이 따로 없었음. 아… 부끄럽네. 아뭏튼 정리해보도록 한다.
property는 간단히 말해, member variable에 대한 encapsulation이라고 할 수 있다. 클래스를 작성할 때, 값을 저장할 변수를 만들고 의식적으로 getter, setter를 만들어 주고 있었지만, 이를 언어 레벨에서 지원하는게 property이다. member variable에 대한 일종의 proxy라고도 할 수 있겠다.
메소드들의 상속은 딱히 헷갈릴 부분이 없는데, member variable과 property는 좀 헷갈린다. 대단한건 없고, property는 getter/setter로 이루어진 메소드로 생각하면 단순해진다. 이제, 언어별 사용법을 알아보자.
Kotlin
코틀린에선 var, val등으로 클래스 변수를 만들면 자동으로 default getter/setter(이후로 accessor로 표기)가 쓰인 property가 사용된다. 그래서 보통의 경우에는 backing field를 고려할 필요가 없다. 특별히 accessor가 필요한 경우에는 다음과 같이 사용한다. (코틀린 공식문서 참조)
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // parses the string and assigns values to other properties
}
코틀린에서 single line function은 중괄호없이 ‘=’를 사용하여 사용가능하다. accessor를 사용할 때, 주의할점은 다음과 같이 써서는 안된다는 사실이다.
var name: String
set(value) {
name = value
}
set() 내의 name = value가 다시 set()을 호출하게되어 무한 재귀호출이 발생한다. 이렇게 쓰고자 할때는 backing field에 직접 값을 할당해야 하는데, backing field를 직접 쓰려면 property 이름을 쓰지말고 ‘field’ 키워드를 사용한다.
var name: String
set(value) {
field = value
}
field 키워드 만으로는 표현이 불가능할 때, Java에서 쓰듯이 backing property를 사용하면 된다. private property를 만들고, 같은 이름으로 하되, 이름앞에는 ‘_'(underscore)를 붙여 사용한다. accessor는 이 private property를 직접 다룬다.
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // Type parameters are inferred
}
return _table ?: throw AssertionError("Set to null by another thread")
}
property의 상속은 method와 동일하게 ‘open’, ‘override’ 키워드로 가능하다. 만약 read-only로 쓰고자 하면, C#같은 곳에서는 get/set 중 하나만 쓰거나 private같은 access modifier를 사용하여 제어하지만, Kotlin에서는 val로 정의한다. 초기화 이후에는 읽을수 있어 read-only property가 된다.
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
property를 override하게되면 다른 override와 마찬가지로, subclass에 새로운 backing field가 생성된다. 그렇기 때문에 subclass의 property값을 변경해도 superclass는 변화가 없게된다.
override하지만, super class의 backing field를 공유하고 싶으면, 다음과 같이 accessor를 만들어준다.( 스택 오버플로우 관련 질의 참조 )
open var items: List<I> = listOf()
set(value) {
field = value
onDataChanged()
}
override var items: List<Int>
get() = super.items
set(value) {
super.items = value
//Your code
}
sub class의 accessor가 super class의 property를 사용하므로 별개의 backing field가 생성되지 않는다고 한다.
C#
C#에서는 accessor를 명시해줘야 property로 사용된다. Kotlin처럼 자동생성되는 backing field를 쓰는 가장 단순한 방법은 다음과 같다. ( MS 공식 문서 참조 )
public string Name
{ get; set; }
public decimal Price
{ get; set; }
body를 갖는 accessor를 명시적으로 사용하고자 하는 경우는 Kotlin하고 비슷한데, property 자체가 중괄호 블럭을 갖는다.
using System;
class TimePeriod
{
private double _seconds;
public double Hours
{
get { return _seconds / 3600; }
set {
if (value < 0 || value > 24)
throw new ArgumentOutOfRangeException(
$"{nameof(value)} must be between 0 and 24.");
_seconds = value * 3600;
}
}
}
Kotlin에서 single-line function 정의와 유사하게 간단한 accessor는 한줄로 표시가 가능한데, expression-bodied member라고 부른다. read-only 멤버 변수의 경우가 대표적인 예다. 사용법은 다음과 같이 ‘=>’를 사용한다.
public class Person
{
private string _firstName;
private string _lastName;
private int _age;
public Person(string first, string last, int age)
{
_firstName = first;
_lastName = last;
Age = age;
}
public string Name => $"{_firstName} {_lastName}";
public int Age
{
get => _age;
set => _age = value;
}
}
Kotlin에선 property를 val로 정의하느냐, var로 정의하느냐에 따라 read-only, read-write로 결정되었었다. C#에서는 get이없으면, write-only, set이 없으면 read-only값이 된다. 또는, private같은 access modifier를 이용하여 컨트롤도 가능하다.
C#에서 property의 상속은 virtual, abstract 와 override 키워드를 사용하여 구현한다. 다른 메소드의 override와 동일하게 생각하면 되며, 마찬가지로 virtual로 정의한 값은 override를 안해도 상관이 없다.
abstract class Shape
{
public abstract double Area
{
get;
set;
}
}
class Square : Shape
{
public double side;
//constructor
public Square(double s) => side = s;
public override double Area
{
get => side * side;
set => side = System.Math.Sqrt(value);
}
}
virtual 사용예는 다음과 같다. ( MS 공식문서 참조 )
class MyBaseClass
{
// virtual auto-implemented property. Overrides can only
// provide specialized behavior if they implement get and set accessors.
public virtual string Name { get; set; }
// ordinary virtual property with backing field
private int num;
public virtual int Number
{
get { return num; }
set { num = value; }
}
}
class MyDerivedClass : MyBaseClass
{
private string name;
// Override auto-implemented property with ordinary property
// to provide specialized accessor behavior.
public override string Name
{
get
{
return name;
}
set
{
if (!string.IsNullOrEmpty(value))
{
name = value;
}
else
{
name = "Unknown";
}
}
}
}
Python
Python에서 property는 @property decorator를 메소드에 붙여주면 된다. 이렇게 해주면 추가로 setter와 deleter도 정의 가능하다. 사용법은 다음과 같다. ( Python 공식문서 참조 )
class C:
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
코드에서 보이듯이, @property 이후로 @x.setter, @x.deleter 등을 이용할 수 있다.
python에선 private은 ‘_'(underscore) , protected는 ‘__'(double underscore)를 붙여주면 비슷하게 동작한다. 또한, duck typing을 사용하므로 virtual, abstract가 없다. abs 모듈을 써서 abstract를 흉내내거나, 직접 raise NotImplementedError()를 써서 비슷하게 흉내를 낸다. 이를 이용해서 필요한 형태로 상속을 사용하면 된다.