상속과 관련해서 생성자는 어떻게 연결이 되는지 헷갈릴만한 포인트들이 있어 한 번 정리해 놓는다.
Kotlin
코틀린은 생성자에 클래스이름을 쓰지않고 ‘constructor’라는 키워드를 쓴다. 여러 생성자를 가질 수 있으나, primary constructor는 클래스 첫 라인을 활용한다. ( Kotlin 공식문서 참조 )
class Person constructor(firstName: String) { /*...*/ }
class Person(firstName: String) { /*...*/ }
class Person(var firstName: String) { /*...*/ }
두번째 라인처럼 키워드를 안써줘도 된다. 또한, 세번째처럼 class property정의를 써서 추가적인 할당코드없이 간단하게 사용도 가능하다.
조금 당황스러울 수 있는 점은 생성자의 바디블럭이 없다는 점인데, 단순한 값의 초기화는 별도의 표시없이 클래스 멤버변수 정의에서 생성자로 넘어온 값을 사용할 수 있다. 추가로 실행해야 할 코드가 있는 경우, init{} 블럭을 사용한다.
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
변수에 값을 대입하는경우, 생성자로 넘어온 name을 그대로 사용했고, init{} 블럭의 예를 들기위해 문장을 출력하고 있다. 실행해보면, 코드 순서대로 실행된다.
추가적인 생성자를 정의하고 싶을 땐 다음과 같이 constructor 키워드를 사용한다.
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
secondary constructor를 만드는경우, this를 사용하여 primary constructor를 불러줘야한다. init블럭이 있는경우, primary 생성자가 불린 이후에 순서대로 실행된다.
상속을 받는 경우에도 secondary constructor와 유사하다.
open class Parent(p: Int)
class Derived(p: Int) : Parent(p)
primary constructor를 사용하는 경우, 위와같이 사용이된다. secondary constructor를 사용하는 경우, 다음과 같이 super 키워드를 사용한다.
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
C#
C++ 이나 Java에서 봐왔듯이, 클래스 이름으로 생성자를 만든다. ( MS 공식문서 참조 )
public class Person
{
private string last;
private string first;
public Person(string lastName, string firstName)
{
last = lastName;
first = firstName;
}
// Remaining implementation of Person class.
}
일반적인 사용시 public 을 반드시 사용해야 하는점을 주의하자. access modifier가 없으면, 기본값이 Internal이 사용되고 외부에서 생성자를 호출하지 못한다. singleton을 만드는 경우에는 private을 사용할 수도 있다.
메소드 오버로딩과 같이, 생성자도 인자에 따라 여러개를 정의해 사용가능하다.
상속을 하는 경우, base 키워드를 사용하여 부모 클래스 생성자를 호출한다. C#에서는 super 키워드가 없고 대신 base를 쓴다.
public class Manager : Employee
{
public Manager(int annualSalary)
: base(annualSalary)
{
//Add further instructions here.
}
}
코틀린에서 secondary constructor가 primary constructor를 부르듯 C#도 자기자신의 다른 생성자를 부를 수 있다. 이 때는 코틀린과 동일하게 this를 사용한다.
public Employee(int weeklySalary, int numberOfWeeks)
: this(weeklySalary * numberOfWeeks)
{
}
Python
파이썬 클래스는 다른 언어들과는 형태가 좀 많이 다르다. 생성자는 __init__ 를 사용하는데, dynamic type 언어기 때문에 오버로딩이 안된다.
class Component:
def __init__(self, name: str, pos: Position):
self._name: str = name
self._position: Position = pos
...
python에서 메소드들의 첫번째 인자는 무조건 self이다. 메소드를 호출할 때, 자동으로 채워진다. member variable은 self를 무조건 붙여서 사용한다. python 특성상, 따로 정의하는 부분이 없기 때문에 보통 __init__ 안에서 전부 정의하고 초기화를 진행한다. 만약에 __init__ 나 다른 메소드에 속하지 않도록 정의하면 class variable이 된다.
상속을 사용하는 경우, 다음과 같이 사용한다.
class Composite(Component):
def __init__(self, name: str, pos: Position):
super().__init__(name, pos)
self._leafs: List[Component] = []
...
Python에서 클래스 상속은 괄호안에 부모 클래스를 표기해준다. Composite class가 Component class를 상속하고 있는걸 볼 수 있다.
클래스 내에서 super()를 사용하여 부모 클래스를 사용할 수 있는데, 생성자 코드내에서 suer().__init__()를 사용하여 부모클래스의 생성자를 호출해 주도록 한다. 다른 언어들에서도 생성자가 부모 클래스를 따라 체인을 이루듯 호출해 주게 되는데, 여기서도 마찬가지다.
Python에서 가능한 다중상속을 살펴볼텐데, 그전에 잠깐 언급할 내용이있다. 여기서 자세히 다루지는 않지만 Python은 깊게 파고들면, 클래스나 상속에 대해 다른 모습들이 나타난다. class도 하나의 객체에 지나지 않으며, 그 최상위에는 meta class가 존재한다. 실제 상속관계도 이 meta class에 영향을 받는다. Python이 다중상속을 지원하지만, meta class는 다중으로 상속받지 못한다. 그래서 다른 meta class를 갖는 두 클래스로부터의 상속은 주의가 필요하다.
보통의 경우에 metaclass를 직접 다루지는 않으므로 다중상속이 가능하고 이 경우 MRO(method resolution order)라는걸 체크해봐야 한다. ( Python-course.eu 사이트 참조 )
class B:
def x(self):
print('x: B')
class C:
def x(self):
print('x: C')
class D(B, C):
pass
d = D()
d.x()
print(D.mro())
위의 코드를 실행해보면, class B의 x()가 호출되는걸 알 수 있다. 그리고 mro에서 B class가 우선되는 것 또한 볼 수 있다.
다중상속시 같은 이름의 메소드가 있다면, super대신에 호출하고 싶은 클래스를 붙여서 사용하면 된다.
class D(B,C):
def m(self):
print("m of D called")
B.m(self)
C.m(self)
다중상속에서는 다이아몬드 상속을 꼭 짚고 넘어가야한다.
class A:
def __init__(self):
print("A.__init__")
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
class D(B,C):
def __init__(self):
print("D.__init__")
super().__init__()
d = D()
D.__init__
B.__init__
C.__init__
A.__init__
상식적으로 생각했을 때, B의 생성자가 호출되고 C의 생성자가 호출되므로 A의 생성자가 두번 호출될 것 같지만, 마지막에 한번만 호출되고 있다. 이는 super()가 무조건 부모 클래스의 함수를 호출하지 않고, MRO 정보를 이용하기 때문이다. 실제로 D의 MRO정보를 출력해보면 다음과 같다.
>>> D.mro()
[<class 'super_init.D'>, <class 'super_init.B'>, <class 'super_init.C'>, <class 'super_init.A'>, <class 'object'>]
앞에서 언급한 meta class는 다중상속 받을 수 없는 이유가 이 MRO가 꼬이기 때문이라 생각된다.