전체 페이지뷰

2016년 12월 16일 금요일

C# Class, Part 2

계속해서 클래스에 대해 공부하겠습니다.


상속

역시 OOP의 가장 중요한 특징 중 하나입니다. 말 그대로 하나의 클래스가 자신의 특성을 다른 크래스에게 물려주는 것을 말합니다.

class 기반클래스
{
    //멤버선언
}

class 파생클래스: 기반클래스
{
    //private으로 선언된 맴버를 제외하고 모든 멤버를 물려받음
}

과 같은 구조를 지닙니다.

// 기반클래스
public class Animal
{
   public string Name { get; set; }
   public int Age { get; set; }
}
// 파생클래스
public class Dog : Animal
{       
   public void AgeIs() 
   {
      // 기반클래스의 Age 속성 사용
      Console.WriteLine("나이: {0}"this.Age);
   }
}
public class Bird : Animal
{       
   public void Fly()
   {
      // 기반클래스의 Name 속성 사용
      Console.WriteLine("{0}이/가 납니다"this.Name);
   }
}
cs

Animal이라는 클래스로부터 Dog과 Bird라는 파생클래스가 생겨났습니다. 각각 특별히 속성을 만들지 않았지만 기반클래스인 Animal로 부터 Name과 age라는 두 속성을 물려받아 사용이 가능합니다.

Animal 클래스에 get; set;이라는 새로운 문법이 보이는데 그것은 차후에 다시 살펴보기로 합니다. Dog, Bird 클래스에서 자신을 지칭하는 키워드로 전에 살펴본 'this'라는 것이 나왔는데, 상속받은 기반클래스를 지칭하려면 'base'라는 키워드를 사용하면 됩니다.


sealed

sealed는 클래스가 상속을 허용하게 하고 싶지 않을 때 사용합니다.

class A {}      
sealed class B : A {}  
cs
와 같이 하면 클래스 A는 상속이 가능하지만 B는 상속시키는 것이 불가능합니다.

Base class와 Derived class 사이의 형식변환

// 사람 클래스
class Human
{
    public string Name;
    public int Age;
}
// 아기(사람을 상속받음) 클래스
class Baby :Human
{
    public void Cry()
    {
        Console.WriteLine("응애응애");
    }
}
// 어른(사람을 상속받음) 클래스
class Adult : Human
{
    public void Work()
    {
        Console.WriteLine("바쁘다 바빠");
    }
}
cs

세개의 클래스를 정의했습니다. 하나는 base인 Human 클래스이고,
그 Human클래스를 상속받아 어른과 아기 클래스를 구현했습니다.

지금 까지 우리는

Human human = new Human();
Baby baby = new Baby();
Adult adult = new Adult();
처럼 인스턴스를 생성하여 사용해 왔습니다. 그러나 아기와 어른은 인간을 상속받은 것이기 때문에 위쪽 방향으로 클래스의 형식변환이 가능해집니다.

다시 말해,

using System;
class Program
{
    class Human
    {
        public string Name;
        public int Age;
        public void Think()
        {
            Console.WriteLine("나는 생각한다. 고로 존재한다.");
        }
    }
    class Baby :Human
    {
        public void Cry()
        {
            Console.WriteLine("응애응애");
        }
    }
    
    class Adult : Human
    {
        public void Work()
        {
            Console.WriteLine("바쁘다 바빠");
        }
    }
    static void Main(string[] args)
    {
        Human human = new Human();
        human.Think();
        human = new Baby();
        human.Think();
        Baby baby = (Baby)human;
        baby.Think();
        baby.Cry();
        human = new Adult();
        human.Think();
        Adult adult = (Adult)human;
        adult.Think();
        adult.Work();
    }   
}
cs

위와 같이 기본 클래스와 파생 클래스 간을 오가는 형식의 변환이 가능해진다는 뜻입니다.

안전한 형변환을 위해 is와 as라는 연산자가 제공됩니다. 
is는 객체의 형식을 검사해서 bool로 반환하며,
as는 형변환에 실패시 null을 반환합니다.

Human human1 = new Baby();
Baby baby;
if (human1 is Baby)
{
    baby = (Baby)human1;           
}
Human human2 = new Human();
Adult adult = human2 as Adult;
if (adult != null)
{
    adult.Work();
}
else
{
    Console.WriteLine("형변환 실패");
}
cs



다형성

OOP의 중요 요소 중 하나인 다형성에 대해서 알아봅니다.
하나의 클래스를 상속 받아 파생 클래스를 만들고 나서, 동시에 상속 받은 멤버(필드는 오버라이딩 불가능. 메소드, 속성, 이벤트만 가능)를 자신만의 것으로 고쳐서 사용 가능합니다. 그것을 오버라이딩이라고 합니다.

이 때, 오버라이드의 대상이 될 부모 멤버는 미리 'virtual'이라는 키워드로 수식하여 오버라이딩이 가능함을 선언해 주야 합니다.

public class Shape
{
    
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }
       
    // Virtual 메소드
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}
class Circle : Shape
{
    public override void Draw()
    {
        // 원
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // 사각형
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
cs

만약 해당 메소드를 이름만 같고 전혀 다르게 구현하고 싶다면 "override" 대신 "new"를 사용해도 좋습니다.
그러나, 둘에는 차이가 있는데 override로 구현하면 부모클래스에서 해당 메소드를 호출해도 새롭게 정의된 메소드가 호출되지만, new로 하면 부모에서는 원래 메소드, 파생 클래스에선 새 메소드가 호출됩니다.

더 이상 오버라이드를 허용하고 싶지 않다고 하면 "sealed"라는 키워드를 사용합니다.

    
public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}
public class C : B
{            
public sealed override void DoWork() { }
}
cs

그러면 클래스 C는 자신에게서 파생된 클래스로부터 더 이상 오버라이드를 허용치 않습니다.



Nested Class

클래스 내부에서 선언된 클래스를 중첩 클래스라고 부릅니다.
 class Container
        {
            class Nested
            {
                Nested() { }
            }
        }
위와 같은 형식입니다.

중첩 형식은 소속된 클래스의 내부 멤버에 모두 자유로이 접근 가능하며, 자기 자신은 기본적으로 private로 지정되어 밖에서는 보이지 않습니다.


Partial Class

큰 프로젝트를 수행 시, 둘 이상의 프로그래머가 하나의 긴 클래스를 작업할 수 있습니다.
이렇게 둘 이상의 소스에 나뉘어 정의된 클래스를 분할 클래스라고 하며, 컴파일 시에 하나로 합쳐집니다.
    public partial class Employee
    {
        public void DoWork()
        {
        }
    }

    public partial class Employee
    {
        public void GoToLunch()
        {
        }
    }
와 같이 "partial" 키워드와 함께 정의합니다. (사실 클래스에만 가능한 것은 아니며 구조체, 인터페이스, 심지어 메소드에서도 가능합니다.

Extension Method

새 파생 클래스를 만들거나 원래의 것을 수정하지 않고 기존 클래스에 메소드를 추가하는 방법입니다. 이를 사용하면 기존에 쓰던 string 클래스에 문자열을 처리하는 메소드를 추가하거나, int 형식에 제곱 기능을 추가하거나 하는 등의 기능을 수행할 수 있습니다.

메소드는 static이어야 하며, 첫번째 매개변수는 this+대상형식+식별자이어야 하고, 두번째 부터가 호출시 쓰이는 매개변수 입니다.

namespace 네임스페이스명
{
    public static class 클래스명
    {
        public static 반환형식 메소드명(this 대상형식 식별자, 매개변수목록)
        {
              //코드;
        }
    }
}
와 같은 형식을 사용하게 되며, 초반부의 using에 작성한 네임스페이스를 담아 줍니다.
설명만으로는 이해하기 어려우니 예제를 봅시다.

using System;
using MyExtension;
namespace MyExtension
{
    public static class StringExtension
    {
        public static int WordCount(this string str)
        {
            return str.Split(new char[] { ' ''.''?' },StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}  
class Program
{
    static void Main(string[] args)
    {
        string s = "Hello Extension Methods";
        int i = s.WordCount();
        Console.WriteLine("\"{0}\"는 {1} 개의 단어로 이루어져 있습니다.", s, i);
    }
}
cs

string에 단어의 수를 세는 WordCount라는 확장메소드를 적용한 모습입니다.


구조체(structure)

클래스 말고도 복합데이터 형식의 하나로 구조체라는 것이 있습니다. C 언어에서도 이 구조체라는 것이 있는데 그것과는 상당히 다릅니다. 내부에 필드말고도 메소드를 가질 수 있다는 것이 그것입니다.

그렇다면 클래스와는 무엇이 다를까, 일단 형식은 다음과 같습니다.

struct 구조체명
{
    //필드, 메소드...;
}

public struct Book  
{  
    public decimal price;  
    public string title;  
    public string author;  
}  
cs

그러나 구조체는 참조가 아닌 값형식입니다. 따라서 대입만으로도 깊은 복사가 가능하며, 인스턴스 생성이 필요없습니다.

using System;
public struct Point3D
{
    public int X, Y, Z;
    public Point3D(int X,int Y,int Z)
    {
        this.X = X;
        this.Y = Y;
        this.Z = Z;
    }
    public override string ToString()
    {
        return string.Format("{0}, {1}, {2}", X, Y, Z);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Point3D p1;  //new 없이도 선언만으로 생성가능
        p1.X = 10;
        p1.Y = 20;
        p1.Z = 30;
        Console.WriteLine(p1.ToString());
        Point3D p2 = new Point3D(100200300);//new로도 생성가능
        Point3D p3 = p2; //deep copy
        p3.Z = 400;
        Console.WriteLine(p2.ToString());
        Console.WriteLine(p3.ToString());
    }
}
cs
결과)
10, 20, 30
100, 200, 300
100, 200, 400


댓글 없음:

댓글 쓰기