Post

[C#] Lec 06 - 객체지향

[C#] Lec 06 - 객체지향

객체

  • 세상에 존재하는 모든 것은 객체로 표현가능.
  • 객체의 특징은 속성과 기능으로 구분됨.

객체 지향 언어의 특징

  • 캡슐화(Encapsulation) : 데이터(속성)과 알고리즘(기능)을 하나로 묶어 사용하는 것
  • 상속(Inheritance) : 상위 클래스의 모든 것을 하위 클래스가 이어 받는 것. 부모가 자식에게 유전자를 물려주는 것과 동일
  • 추상화 (abstraction) : 객체의 공통적인 속성과 기능을 추출하여 정의하는 것. - 어떤 객체를 속성과 기능으로 정의하는 것.
  • 다형성(polymorphism) : 한 객체가 다른 여러 형태로 재구성 가능한 것을 의미함

객체 지향 언어의 장점과 단점

  • 장점 :
    1. 재사용성 : 상속을 통해 코드 재사용을 높일 수 있음
    2. 생산성 향상 : 클래스 잘 설계하여 객체 사용, 개발의 효율성이 높아짐
    3. 자연스러운 모델링 : 일반적으로 사람이 생각하는 모습의 구조와 객체가 표현가능. 자연스러운 코드의 표현
    4. 유지보수성 : 코드의 추가, 수정 시 캡슐화를 통해 주변에 영향 적어 유지보수에 용이
  • 단점 :
    1. 개발속도가 느림 : 객체에 대한 정확한 이해 필요, 코드 설계단계 시간 소요
    2. 실행 속도가 느림 : 데이터의 복잡한 접근구조로 실행의 속도가 느림.
    3. 코딩 난이도 상승 : 개념 복잡.

클래스

  • 네임스페이스 안쪽에 정의되어야 함.
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.