[C#] Lec 06 - 객체지향
[C#] Lec 06 - 객체지향
객체
- 세상에 존재하는 모든 것은 객체로 표현가능.
- 객체의 특징은 속성과 기능으로 구분됨.
객체 지향 언어의 특징
- 캡슐화(Encapsulation) : 데이터(속성)과 알고리즘(기능)을 하나로 묶어 사용하는 것
- 상속(Inheritance) : 상위 클래스의 모든 것을 하위 클래스가 이어 받는 것. 부모가 자식에게 유전자를 물려주는 것과 동일
- 추상화 (abstraction) : 객체의 공통적인 속성과 기능을 추출하여 정의하는 것. - 어떤 객체를 속성과 기능으로 정의하는 것.
- 다형성(polymorphism) : 한 객체가 다른 여러 형태로 재구성 가능한 것을 의미함
객체 지향 언어의 장점과 단점
- 장점 :
- 재사용성 : 상속을 통해 코드 재사용을 높일 수 있음
- 생산성 향상 : 클래스 잘 설계하여 객체 사용, 개발의 효율성이 높아짐
- 자연스러운 모델링 : 일반적으로 사람이 생각하는 모습의 구조와 객체가 표현가능. 자연스러운 코드의 표현
- 유지보수성 : 코드의 추가, 수정 시 캡슐화를 통해 주변에 영향 적어 유지보수에 용이
- 단점 :
- 개발속도가 느림 : 객체에 대한 정확한 이해 필요, 코드 설계단계 시간 소요
- 실행 속도가 느림 : 데이터의 복잡한 접근구조로 실행의 속도가 느림.
- 코딩 난이도 상승 : 개념 복잡.
클래스
- 네임스페이스 안쪽에 정의되어야 함.
1
2
3
4
5
6
7
8
9
class 클래스이름 // 클래스이름은 대문자로 시작.
{
public string _name; //필드, naming은 언더바+소문자. 이름의 정의
//Data and Methods
public void adsf() //메소드
{
//code
}
}
- class는 추상화된 것이므로 instance를 만들어야지만 클래스를 사용할 수 있음.
생성자와 소멸자
- 클래스의 객체를 생성할 때 생성자가 호출됨
- 생성자를 명시적 구현하지 않아도 자동으로 컴파일러가 만들어 줌
- 객체 생성의 시점에서 객체를 초기화하기 위해 주로 생성자 사용.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Dog
{
public Dog()
{
_name = "";
_color = "";
}
public Dog(string n, string c) //생성자의 오버로딩 가능.
// 생성자 또한 이름과 매개변수로 그 차이 판단.
{
_name= n;
_color=c;
}
// 명시적으로 생성자를 선언하는 순간 기본 생성자 Dog()는 사용할 수 없음. (기본 생성자)
public string _name;
public string _color;
}
- class 속에서 만들어 준 변수 또한 참조형 변수이므로, new를 집어넣어야만 함.
- 소멸자 : ~를 이용하여 사용.
- 객체가 소멸될 때 소멸자가 호출됨.
- 매개 변수 설정 불가능, 한정자의 사용 불가능, 오버로딩 불가능, 직접 호출 불가능
- 가비지 컬렉터에 의해 자동으로 호출, 거의 사용 되지 않음.
1
2
3
4
5
6
7
class Dog
{
~클래스이름()
{
//소멸자 코드
}
}
정적 필드와 메소드
- 인스턴스에 소속된 필드 / 클래스에 소속된 필드
- 객체의 속성을 정할 때 객체가 지정되지 않으면 속성이 정해지지 않지만, 그 클래스 자체의 속성을 정의할 수도 있음.
1
2
3
4
5
6
class Dog
{
public string _name;
public string _color; // Dog라는 instance에 포함되는 속성, Dog 이름 = new Dog() 정의 후에만 사용가능
public static string _spicies = "mammel"; //class에 포함되는 속성
}
- class 내에서 정의되는 정적 메소드와 동일한 개념.
- C#에서 static은 메소드나 필드가, class의 instance가 아닌, class 자체에 소속되도록 지정하는 한정자임
- 한 프로그램 내에 클래스는 단 하나만 존재, 어떤 필드가 클래스 자체에 소속 → 프로그램 전체에 그 필드가 유일하게 존재. (클래스는 오버로딩이 안된다고 생각해야 할듯)
- Static으로 한정되지 않으면 자동으로 해당 인스턴스에 소속
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dog
{
public void TestMethod()
{
//인스턴스 내에 소속된 메소드
}
public static void Testmethod2() // Testmethod2에서는 _name은 사용불가능, _name2는 사용가능. static 한정자의 유무에 따라.
{
//클래스에 소속된 메소드, 정적 메소드
}
public string _name;
public static string _name2;
}
public static Main()
{
Dog Name = new Dog();
Name.TestMethod(); // TestMethod를 불러오기 위해 Dog를 먼저 선언해주어야 함.
Dog.Testmethod2(); // 변수 선언 없이도 Dog class 내에서 선언된 Dog를 사용가능.
}
- 객체 내부의 데이터 이용해야 하는 경우는 인스턴스 메소드 사용
얕은 복사와 깊은 복사
- 클래스는 모두 참조형식으로 객체가 할당됨.
- 객체의 복사(할당) 시 주소만을 복사. 즉 Heap의 주소가 같은 곳을 가리키는 얕은 복사(shallow copy)가 이루어짐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass
{
public int MyField1;
public int MyField2;
}
public static Main()
{
MyClass alpha = new MyClass();
alpha.MyField1=10;
alpha.MyField2=20;
MyClass beta = alpha;
beta.MyField2 = 30;
Console.WriteLine(alpha.MyField2); //30 출력.
}
- 깊은 복사를 위해서는 이를 수행하기 위한 별도의 코드를 만들어 주어야 함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Program
{
static void Main(string[] args)
{
MyClass alpha = new MyClass();
alpha.MyField1 = 10;
alpha.MyField2 = 20;
MyClass beta = alpha.DeepCopy();
beta.MyField2 = 30;
Console.WriteLine(beta.MyField2); //20 출력
Console.WriteLine(alpha.MyField2); //30 출력.
}
}
class MyClass
{
public int MyField1;
public int MyField2;
public MyClass DeepCopy()
{
MyClass temp = new MyClass();
temp.MyField1 = this.MyField1;
temp.MyField2 = this.MyField2;
return temp;
}
}
- DeepCopy() method의 작동 : temp라는 MyClass 생성, this.MyField1, 2를 통해 temp의 MyField1,2에 속성값들을 받아옴, return temp를 통해 alpha.DeepCopy() 자리에 temp를 대입, MyClass beta = temp 에서 beta가 원래 temp였던 heap을 가리키도록 주소가 바뀜. temp는 코드블록 밖으로 나왔으니 사라짐.
- this 키워드의 사용 : this 를 사용함으로써 자기 자신의 속성을 사용한다는 의미, 혼동의 방지. 굳이 안써도됨
this 생성자의 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MyClass
{
public int MyField1;
public int MyField2;
public MyClass()
{
MyField1=0;
this.MyField2=0; // this keyword 사용해도 안해도 무방
Console.WriteLine(1);
}
public MyClass(int a) : this()
{
MyField1=a;
// : this()를 쓰지 않았다면, MyField2=0;을 사용했어야 함.
Console.WriteLine(2);
}
public MyClass(int a, int b) : this(a)
{
this.MyField2=b; //여기도 this 사용해도 안해도 무방.
// : this(a)를 쓰지 않았다면, MyField1=a;를 사용했어야 함.
Console.WriteLine(3);
}
}
class Program
{
static void Main(string[] args)
{
MyClass alpha = new MyClass(1, 2); // 1, 2, 3 순서로 출력됨.
}
}
접근 한정자의 사용 : public, private, protected
- public : 클래스 내,외부 전체에서 접근 가능
- private : 클래스 내부에서만 접근가능. 파생 클래스에서 접근 불가능 (접근 한정자 미설정시 기본값)
- protected : 클래스 내부에서만 접근 가능, 파생 클래스에서 접근 가능
- 외부에서 따로 수정은 불가능하나, 생성자를 통해서만 접근할 수 있도록 설정할 수도 있음
- private 변수에 접근하기 위하여 setter, getter method를 이용하여 접근함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyClass
{
public int MyField1;
private int MyField2;
public MyClass(int a, int b)
{
this.MyField1=a;
this.MyField2=b;
}
public void SetClass(int a)
{
this.MyField2 = a;
}
public int GetClass()
{
return MyField2;
}
}
class Program
{
static void Main(string[] args)
{
MyClass alpha = new MyClass(1, 2);
Console.WriteLine(alpha.GetClass()); // 2 출력 (private field인 MyField2에 접근)
alpha.SetClass(5); // alpha의 private인 MyField2를 5로 수정
Console.WriteLine(alpha.GetClass()); // 5
}
}
상속
- private 로 선언된 멤버 이외에 모든 것을 동일하게 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Parent
{
public void ParentMethod()
{
Console.WriteLine("ParentMethod");
}
}
class Child : Parent
{
// ParentMethod 구현하지 않아도 사용가능
}
class Program
{
public static Main() {
Child a = new Child();
a.ParentMethod(); //ParentMethod 출력
}
}
- 생성자는 상속되지 않음. 생성자를 통해 private field에 접근할 수 있는 것이므로, 생성자를 상속받아버리면 간접적으로 child 가 parent의 private field에 접근하는 것이 되므로.
- 대신 생성자에서 앞선 : this(args)를 사용한 것 과 같이 base(args)를 사용하여 부모 클래스의 생성자에 접근할 수 있음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
ChildClass alpha = new ChildClass(34);
Console.WriteLine(alpha.GetField3()); //-3
alpha.Func();//alpha의 MyField3를 1로 바꿔줌 protected이므로 가능.
Console.WriteLine(alpha.MyField1); //public이므로 그냥 34출력
Console.WriteLine(alpha.GetField2());// 4 출력
Console.WriteLine(alpha.GetField3());// 1 출력 getter 함수 이용한것.
}
}
class MyClass
{
public int MyField1;
private int MyField2;
protected int MyField3;
public MyClass()
{
this.MyField1 = -1;
this.MyField2 = -2;
this.MyField3 = -3;
Console.WriteLine("Parent 생성자0 통과");
}
public MyClass(int a) : this()
{
this.MyField1 = a;
Console.WriteLine("Parent 생성자1 통과");
}
public MyClass(int a, int b) : this(a)
{
this.MyField2 = b;
Console.WriteLine("Parent 생성자2 통과");
}
public void SetField2(int a)
{
this.MyField2 = a;
}
public int GetField2()
{
return MyField2;
}
public int GetField3()
{
return MyField3;
}
}
class ChildClass : MyClass
{
public ChildClass(int a) : base(a, 4)
{
Console.WriteLine("Child 생성자 통과");
}
public void Func()
{
//this.MyField2 = 54;
this.MyField3 = 1;
//Private같은 경우는 자식 클래스에서 필드 수정 불가 but protected는 사용가능
}
}
}
- 자녀 클래스에서 생성자와 소멸자의 순서 : 기반 클래스의 생성자 → 파생 클래스의 생성자 → 파생 클래스의 소멸자 → 기반 클래스의 소멸자 순으로 호출이 이루어짐.
- 기반 클래스(부모)의 경우 sealed라는 키워드를 통하여 상속을 봉인할 수 있음
- 부모 클래스에서 매개변수를 받지 않는 생성자, 즉 기본 생성자가 없는 경우 (매개변수가 존재하는 생성자만을 명시적으로 정의한 경우 포함)에는 자식 클래스에서 매개변수가 없는 생성자를 만들 수 없음.
기반 클래스와 파생 클래스의 형 변환
- 기반 클래스 형식으로 파생 클래스의 할당이 가능함. ex)
1
2
3
4
5
6
7
8
9
static void Main(string[] args)
{
Myclass beta;
beta = new ChildClass(1); // 기반 클래스의 형식인 beta에 파생클래스인 ChildClass 할당
ChildClass gamma;
//gamma = new MyClass(); - Error 발생
gamma = (ChildClass) beta; // 클래스간의 명시적 형변환
}
- 형변환을 위해 is, as keyword를 사용할 수 있음.
1
2
3
4
5
6
7
8
9
class ChildClass {
}
static void Main(string[] args)
{
if (beta is ChildClass)
gamma = beta as ChildClass; // 실패하더라도 Null 값을 반환함.
}
- 생성자 없이 속성을 집어넣을 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Dog a = new Dog() {_name= "asdf", _color="asdf"};
Chiwawa b = new Chiwawa() {_name= "asdfd", _color="asddf"};
ZooKeeper z = new ZooKeeper();
z.Wash(a);
z.Wash(b);
// Chiwawa는 Dog의 자식 클래스이므로 부모 클래스의 property(필드)를 public, private 모두 갖고있음
// 그러므로 Dog에 대해서만 Wash method 정의를 해주면 자식 매서드에서도 사용가능.
class ZooKeeper
{
public void Wash(Dog dog)
{
Console.WriteLine($"{dog._name} : 씼는 중");
dog.Bark();
Doberman temp = null;
if (dog is Doberman)
{
temp = dog as Doberman;
temp.Chasing();
}
}
}
// 자식 클래스만 가능한 속성의 경우 당연히 Dog를 인수로 받는 매서드를 만들때 사용 불가능
// 상위클래스 매개변수로 매서드를 만들면 상위클래스 공통으로만 존재하는 속성을 이용해 만든다.
오버라이딩과 다형성
- 기반 클래스에서 이미 정의된 매소드를 파생 클래스에서 변경하기 위해서는 기반 클래스에는 virtual, 파생클래스의 매서드 한정자로 override 를 넣어줘야함.
- 기반 클래스에다가 파생 클래스를 할당한 경우 파생 클래스에서 오버라이딩 된 매서드로 실행됨.
1
2
3
Dog d = new Chiwawa();
d.Bark(); //Chiwawa class에서 override 된 Bark()함수를 실행함.
d.ChiwawaOnly(); // 파생 클래스에서만 정의된 매서드의 경우 오류가 남 예외처리 필요
메소드 숨기기
- 기반 클래스에 접근할 수 없지만 오버라이드 하고싶다면, 메소드 숨기기를 이용하여 오버라이딩과 같은 효과를 얻을 수 있음.
- new 한정자를 파생 클래스의 메소드에 붙여서 메소드 숨기기 가능.
- sealed 한정자를 기반 클래스에 추가하면 오버라이드 할 수 없음.
확장 메소드
- 기존 클래스 기능을 확장하기 위해서 확장 메소드를 사용함.
- 외부라이브러리 사용하면서 사용자 임의 메소드 추가할 때 주로 사용.
1
2
3
4
5
6
7
8
9
10
11
12
13
public static class IntExt
{
public static int Pow(this int a, int exponent)
{
return (int) Math.Pow(a, exponent);
}
}
static void Main(string[] args)
{
int a = 10;
int b = 20;
a.Pow(b); // this int a는 이미 자기자신을 지칭하는 것이므로, 변수에 넣을 필요 X
}
- 반드시 static method로 선언해야함.
This post is licensed under CC BY 4.0 by the author.