레이블이 C인 게시물을 표시합니다. 모든 게시물 표시
레이블이 C인 게시물을 표시합니다. 모든 게시물 표시

2016년 11월 8일 화요일

OOP 추상화(Abstraciton)

OOP의 특징 중 하나가 추상화(Abstraciton)입니다. 그런데 이 추상화가 뭘까요? 피카소의 그림일까요?

'추상화'란 것은, 어떤 기능을 가진 객체를 구현할때, 객체 외부에서는 그 기능의 정확한 묘사를 하지 않는 것입니다. 그 대신 그 기능의 '추상적 행위'를 묘사해야 합니다.

이를테면 다음과 같은 잠수함을 구현한다고 해 봅시다.

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public int GetX() { return x; }
    public int SetX(int x) { this.x = x; }
    ...........
}

void Function(Submarine ship)
{
    // 북쪽으로 이동
    ship.SetY(ship.GetY() - 1);


    // 수면으로 이동
    if(ship.GetDepth() > 0)
        ship.SetDepth(ship.GetDepth() - 1);
}

이런 식으로 해도 동작은 하겠죠. 하지만 이런 구현을 한다면 구태여 OOP 프로그램을 사용할 필요가 없습니다. 그냥 C로도 구현되는 수준이거든요. 오히려 C로 하면 저런 잡다한 멤버함수를 만들 필요도 없이 쉽게 끝납니다(하지만 그냥 C 프로그램에서도 이런 식의 프로그램은 좋지 않습니다*).

이것을 제대로 추상화를 구현한다면 이런 식이 되어야 합니다.

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public void ToEast() { ++x; }
    public void ToWest() { --x; }
    public void ToSouth() { ++y; }
    public void ToNorth() { --y; }

    public void Float()
    {
        if(depth > 0)
           --depth;
    }

    public void Sink() { ++depth; }
}

void Function(Submarine ship)
{
    // 북쪽으로 이동
    ship.ToNorth();

    // 수면으로 이동
    ship.Float();
}


이런 식으로 Submarine 밖에서는 Submarine의 멤버변수 자체를 생각하지 않고 코딩하는 것이 '추상화'입니다. x니 y니 따위를 생각하지 않고 오로지 '북쪽으로 이동'만 코딩하는 것이죠. '구체화'는 Submarine.ToNorth() 안에서 하고 말입니다.
캡슐화(Capsulation)는 멤버변수에 직접 접근이 힘들 뿐, 실제 멤버변수가 뭐가 있는지 알 수는 있다는 점에서, 추상화는 캡슐화보다 한단계 더 진행한 개념이라고 볼 수 있죠.

만약

class Ocean
{
    ......

    public bool IsReef(int x, int y)    // 암초가 있는가
    {
        .....
    }
}

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public int GetX() { return x; }
    public int SetX(int x) { this.x = x; }
    ...........
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ship.SetY(ship.GetY() - 1);

    // 암초에 걸렸는지 체크
    if(ocean.IsReef(ship.GetX(), ship.GetY())
        ship.Destroyed();
}

이런 것은 '캡슐화'는 되었지만(x, y변수가 '캡슐화되었죠) '추상화'가 된 코드는 아닙니다. '추상화'를 시킨다면

class Ocean
{
    ......

    public bool IsReef(int x, int y)    // 암초가 있는가
    {
        .....
    }
}

class Submarine
{
    private int x;    // x좌표
    private int y;    // y좌표
    private int depth;// 깊이

    public void ToEast() { ++x; }
    public void ToWest() { --x; }
    public void ToSouth() { ++y; }
    public void ToNorth() { --y; }

    public bool IsSafe(Ocean ocean)
    {
        if(ocean.IsReef(x, y))  // 암초에 걸렸으면
            return false;
        // 다른 상황 체크

        // 모두 통과했으면 안전
        return true;
    }
    ...........
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ship.ToNorth();

    // 배가 안전한지 체크
    if(ship.isSafe(ocean))
        ship.Destroyed();
}


가 되어야 합니다.


* 위에서 저런 식의 코딩은 C에서도 좋지 않다고 했는데, C에서의 코딩은 다음과 같이 하는 것이 좋습니다.

struct Ocean
{
    ......
}

struct Submarine
{
    int x;    // x좌표
    int y;    // y좌표
    int depth;// 깊이
}

void ToEast(Submarine ship) { ++ship.x; }
void ToWest(Submarine ship) { --ship.x; }
void ToSouth(Submarine ship) { ++ship.y; }
void ToNorth(Submarine ship) { --ship.y; }

bool IsSafe(Ocean ocean, Submarine ship)
{
    if(ocean.IsReef(ship.x, ship.y))  // 암초에 걸렸으면
        return false;
    // 다른 상황 체크

    // 모두 통과했으면 안전
    return true;
}

void Function(Ocean ocean, Submarine ship)
{
    // 북쪽으로 이동
    ToNorth(ship);

    // 배가 안전한지 체크
    if(isSafe(ocean, ship))
        Destroyed(ship);
}

2016년 8월 20일 토요일

동일한 여러개 인수 전달

함수를 호출할 때 컴파일러가 인수 타입을 체크


void Func(int a, ClassTypeA *b, StructTypeB &c, int *d);

main()
{
    int varA;
    ClassTypeA clsB;
    StructTypeB strC;

    Func(varA, &clsB, strC, &varA);  // 컴파일 성공
    Func(&clsB, varA, &varA, strC);  // 컴파일 에러
}


하지만 만약 이 여러개의 인수가 동일한 타입을 가지고 있다면?


void Func(int id, int height, int weight, int birthYear, int score, int familyNumber)
{
    printf("Height %d\n", height);
    printf("Weight %d\n", weight);
}

main()
{
    int Id = 1;
    int weight = 67;      // 몸무게
    int height = 175;    // 키
    int birth = 1992;   // 태어난 해
    int family = 4;    // 가족 수
    int score = 96;   // 성적


    Func(Id, height, weight, birth, score, family);  // 컴파일 성공
    Func(Id, weight, height, birth, family, score);  // 컴파일 성공
    Func(family, height, score, birth, weight, Id);  // 컴파일 성공
}

즉 프로그래머의 실수를 컴파일러가 찾을 수 없다는 뜻.


C/C++

struct Arg
{
    int Id;
    int height;           // 키
    int weight;          // 몸무게
    int birthYear;      // 태어난 해
    int score;         // 성적
    int familyNumber; // 가족 수
};

void Func(Argument *arg)
{
    printf("Height %d\n", arg->height);
    printf("Weight %d\n", arg->weight);
}

main()
{
    Argument arg;
    arg.Id = 1;
    arg.weight = 67;          // 몸무게
    arg.height = 175;        // 키
    arg.birthYesr = 1992;   // 태어난 해
    arg.familyNumber = 4;  // 가족 수
    arg.score = 96;       // 성적


    Func(&arg);  // 컴파일 성공
}

물론

    arg.weight = 175;
    arg.height = 67;

와 같은 실수는 감지 못하지만 이것은 눈으로 볼 수 있으므로 실수할 가능성 적음


Lua

루아에서는 타입 자체가 없으므로 이런 버그가 발생할 가능성 더 큼. 루아에서는 테이블을 이용하여 간단히 구현 가능

function Func(m)
   print("Height " .. m.height)
   print("weight " .. m.weight)
end

Func({Id = 1,
      weight = 67;      -- 몸무게
      height = 175;     -- 키
      birthyear = 1992, -- 태어난 해
      familyNumber = 4; -- 가족 수
      score = 96;       -- 성적
     }
    )


C#

C#에서는 변수 이름으로 인수를 전달하는 기능이 있으므로 위와 같이 인수 순서가 헷갈릴 때 유용함


namespace Space
{
    class Program
    {
        static void Func(int id, int height, int weight,
                         int birthYear, int score, int familyNumber)
        {
            Console.WriteLine("Height {0}", height);
            Console.WriteLine("Weight {0}", weight);
        }
        static void Main(string[] args)
        {
            int Id = 1;
            int weight = 67;      // 몸무게
            int height = 175;    // 키
            int birth = 1992;   // 태어난 해
            int family = 4;    // 가족 수
            int score = 96;   // 성적

            Func(id:Id, score:score, weight:weight,
                 familyNumber:family, height:height, birthYear:birth);  // 컴파일 성공
        }
    }
}