객체지향 프로그래밍 예제
모든길은 인다이렉션으로 통한다.
인다이렉션은 어떤일을 내가 직접하는 것이 아니라 다른 사람에게 부탁한다는 의미이다. 어떤 코드를 작성했는데, 이 코드가 다른 코드를 호출하고, 다른 코드는 또 다른 코드를 호출해서 내가 직접 처리하지 않지만 여러 경로를 거쳐 결국에 결과를 가져오는 식이다.
1.파일이름을 통한 인다이렉션
단어의 길이를 출력하는 프로그램이다.
//단어 개수 출력 1
void showWordsCount (){
const char *words[4] = {"car" , "my book" , "working" , "running"};
int wordCount = 4;
for (int i = 0; i < wordCount; i++) {
NSLog(@"%s 단어 개수는 %lu 개 입니다." , words[i] , strlen(words[i]));
}
printf("\n");
}
2.파일의 단어 변경하기
이 소스의 문제는 코드에 단어를 하드코딩해서 다시 배포를 해야 된다는 점이다.
//단어 개수 출력 2
void showWordsCount2 (){
const char *words[4] = {"my \"big\" car " , "my \"cook\" book" , "working \"hard\" " , " \"happy\" running "};
int wordCount = 4;
for (int i = 0; i < wordCount; i++) {
NSLog(@"%s 단어 개수는 %lu 개 입니다." , words[i] , strlen(words[i]));
}
printf("\n");
}
3.인다이렉션 방법
이번에는 단어를 파일로 분리 했다.
//단어 개수 출력 3
void showWordsCount3 (){
FILE *wordFile = fopen("/Users/hyunhojeong/Desktop/words.txt", "r");
char word[100];
while (fgets(word, 100, wordFile)) {
//printf("%lu \n" , strlen(word));
word[strlen(word) -1] = '\0';
NSLog(@"%s 단어 개수는 %lu 개 입니다." , word , strlen(word));
}
fclose(wordFile);
printf("\n");
}
4.진정한 인다이렉션
읽을 파일이 변경될 수도 있으니깐, 인자로 파일 경로를 받게 해보자.
int main(int argc, const char * argv[]) {
//단어 개수 읽기
showWordsCount();
//단어 개수 읽기 2.
showWordsCount2();
//단어 개수 읽기 3.
showWordsCount3();
//단어개수 읽기 4
if (argc == 1) {
NSLog(@"인자에 파일명을 입력하세요");
return 1;
}
FILE *wordFile = fopen(argv[1], "r");
char word[100];
while (fgets(word, 100, wordFile)) {
//printf("%lu \n" , strlen(word));
word[strlen(word) -1] = '\0';
NSLog(@"%s 단어 개수는 %lu 개 입니다." , word , strlen(word));
}
fclose(wordFile);
printf("\n");
return 0;
}
객체지향 프로그래밍에서 인다이렉션
절차적 프로그래밍 - 도형그리기
절체적인 프로그래밍을 할때는 함수가 다루는 형식의 데이터를 처리하기 위해 함수와 데이터를 연결하는 데 많은 시간을 소비하게 된다. 각 데이터에 맞는 적절한 함수를 사용하는데 주의를 기울여야 한다. 예를들어, kRectangle 형식의 모양을 그릴려면 drawRectangle() 함수를 호출해야 한다. 다른 함수를 호출하면 원하는 결과가 나타나지 않는다.
이러한 코딩 방식의 또 다른 문제점은 프로그램을 유지보수하고 확장하기가 어렵다는 것이다. 삼각형을 넣어야 된다면 어떻게 할까?
(주석으로 된 부분이 삼각형도형을 그리기 위한 코드다.) 적어도 네 부분을 수정해야 한다.
#import <Foundation/Foundation.h>
//절차적 프로그래밍은 함수가 중심이다.
//모양
typedef enum {
kCircle,
kRectangle,
kOblateSpheroid
//kTriangle //삼각형이 추가되면
}ShapeType;
//색상
typedef enum{
kRedColor,
kGreenColor,
kBlueColor
}ShapleColor;
//위치 및 크기 구조체
typedef struct{
int x, y, width, height;
}ShapeRect;
//도형 구조체
typedef struct{
ShapeType type;
ShapleColor fillColor;
ShapeRect bounds;
}Shape;
//ShapleColor 이넘 => NSString 타입 변경
NSString *colorName (ShapleColor colorName){
switch (colorName) {
case kRedColor:
return @"red";
break;
case kGreenColor:
return @"green";
break;
case kBlueColor:
return @"blue";
break;
}
return @"no color";
}
//원그리기
void drawCircle(ShapeRect bounds, ShapleColor fillColor){
NSLog(@"원을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
//사각형 그리기
void drawRectangle(ShapeRect bounds, ShapleColor fillColor){
NSLog(@"사각형을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
//오목원 그리기
void drawEgg(ShapeRect bounds, ShapleColor fillColor){
NSLog(@"오목원을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
/*
//삼각형 그리기
void drawTriangle (ShapeRect bounds,
ShapeColor fillColor)
{
NSLog (@"drawing triangle at (%d %d %d %d) in %@",
bounds.x, bounds.y,
bounds.width, bounds.height,
colorName(fillColor));
}
*/
//해당 모양 함수 찾아서 그리기
void drawShapes(Shape shapes[] , int count){
for (int i = 0 ; i < count; i++) {
switch (shapes[i].type) {
case kCircle:
drawCircle(shapes[i].bounds, shapes[i].fillColor);
break;
case kRectangle:
drawRectangle(shapes[i].bounds, shapes[i].fillColor);
break;
case kOblateSpheroid:
drawEgg(shapes[i].bounds, shapes[i].fillColor);
break;
/*
case kTriangle:
drawTriangle (shapes[i].bounds, shapes[i].fillColor);
break;
*/
}
}
}
int main(int argc, const char * argv[]) {
Shape shapes[3];
ShapeRect rect0 = {0,0,10,30};
shapes[0].type = kCircle;
shapes[0].fillColor = kRedColor;
shapes[0].bounds = rect0;
ShapeRect rect1 = {30,30,50,60};
shapes[1].type = kRectangle;
shapes[1].fillColor = kGreenColor;
shapes[1].bounds = rect1;
ShapeRect rect2 = {15,18,37,29};
shapes[2].type = kOblateSpheroid;
shapes[2].fillColor = kBlueColor;
shapes[2].bounds = rect2;
/*
ShapeRect rect3 = { 47, 32, 80, 50 };
shapes[3].type = kTriangle;
shapes[3].fillColor = kRedColor;
shapes[3].bounds = rect3;
*/
drawShapes(shapes, 3);
return 0;
}
객체지향 방식으로 - 도형그리기
#import <Foundation/Foundation.h>
//모양 - 삭제 됨
//typedef enum {
// kCircle,
// kRectangle,
// kOblateSpheroid
// //kTriangle
//}ShapeType;
//삭제됨
////도형 구조체
//typedef struct{
// //ShapeType type;
// ShapleColor fillColor;
// ShapeRect bounds;
//
//}Shape;
//색상
typedef enum{
kRedColor,
kGreenColor,
kBlueColor
}ShapleColor;
//위치 및 크기 구조체
typedef struct{
int x, y, width, height;
}ShapeRect;
//ShapleColor 이넘 => NSString 타입 변경
NSString *colorName (ShapleColor colorName){
switch (colorName) {
case kRedColor:
return @"red";
break;
case kGreenColor:
return @"green";
break;
case kBlueColor:
return @"blue";
break;
}
return @"no color";
}
// ------------------------------------------------
// 원 관련 클래스
@interface Circle : NSObject
{
ShapleColor fillColor;
ShapeRect bounds;
}
-(void) setFillColor:(ShapleColor) fillColor;
-(void) setBounds:(ShapeRect) bounds;
-(void) draw;
@end
@implementation Circle
-(void) setFillColor:(ShapleColor) c{
fillColor = c;
}
-(void) setBounds:(ShapeRect) b{
bounds = b;
}
-(void) draw{
NSLog(@"원을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
@end //원 클래스
// ------------------------------------------------
// 사각형 관련 클래스
@interface Rectangle : NSObject
{
ShapleColor fillColor;
ShapeRect bounds;
}
-(void) setFillColor:(ShapleColor) fillColor;
-(void) setBounds:(ShapeRect) bounds;
-(void) draw;
@end
@implementation Rectangle
-(void) setFillColor:(ShapleColor) c{
fillColor = c;
}
-(void) setBounds:(ShapeRect) b{
bounds = b;
}
-(void) draw{
NSLog(@"사각형을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
@end //사각형 클래스
// ------------------------------------------------
// 오목원 관련 클래스
@interface OblateSpheroid : NSObject
{
ShapleColor fillColor;
ShapeRect bounds;
}
-(void) setFillColor:(ShapleColor) fillColor;
-(void) setBounds:(ShapeRect) bounds;
-(void) draw;
@end
@implementation OblateSpheroid
-(void) setFillColor:(ShapleColor) c{
fillColor = c;
}
-(void) setBounds:(ShapeRect) b{
bounds = b;
}
-(void) draw{
NSLog(@"오목원을 %@색으로 (%d, %d, %d , %d)에 그리시오" ,
colorName(fillColor) ,
bounds.x , bounds.y, bounds.width , bounds.height);
}
@end //오목원 클래스
/*
삼각형 클래스 추가
// --------------------------------------------------
// 삼각형 클래스
@interface Triangle : NSObject
{
ShapleColor fillColor;
ShapeRect bounds;
}
- (void) setFillColor: (ShapleColor) fillColor;
- (void) setBounds: (ShapeRect) bounds;
- (void) draw;
@end // Triangle
@implementation Triangle
- (void) setFillColor: (ShapleColor) c
{
fillColor = c;
} // setFillColor
- (void) setBounds: (ShapeRect) b
{
bounds = b;
} // setBounds
- (void) draw
{
NSLog (@"drawing a triangle at (%d %d %d %d) in %@",
bounds.x, bounds.y,
bounds.width, bounds.height,
colorName(fillColor));
} // draw
@end // 삼각형
*/
//도형 그리기
void drawShapes(__strong id shapes[] , int count){
for (int i = 0; i < count; i++) {
id shape = shapes[i];
[shape draw];
}
}
int main(int argc, const char * argv[]) {
id shapes[3];
ShapeRect rect0 = {0,0,10,30};
shapes[0] = [Circle new];
[shapes[0] setBounds:rect0];
[shapes[0] setFillColor:kRedColor];
ShapeRect rect1 = {30,40,50,60};
shapes[1] = [Rectangle new];
[shapes[1] setBounds:rect1];
[shapes[1] setFillColor:kGreenColor];
ShapeRect rect2 = {15,19,37,29};
shapes[2] = [OblateSpheroid new];
[shapes[2] setBounds:rect2];
[shapes[2] setFillColor:kBlueColor];
/*
//삼각형 추가
ShapeRect rect3 = { 47, 32, 80, 50 };
shapes[3] = [Triangle new];
[shapes[3] setBounds: rect3];
[shapes[3] setFillColor: kRedColor];
*/
drawShapes(shapes, 3);
return 0;
}
drawShapes()와 절차적 함수의 차이를 살펴보자. 우선 이쪽이 더 길이가 짧다. 아울러 반복해서 어떤 종류의 모양인지 확인하지 않는다.
다른 변화는 함수의 첫번째 인수인 shapes[] 인데, id 객체의 배열이다. id는 어떤 객체든 참조할 수 있는 일반적인 타입이다. (객체는 단지 c 구조체에 약간의 코드를 추가한 것이다.) 그러므로 id는 실제로 이런 구조체 중 하나를 가리키는 포인터다. 이 프로그램의 경우에는 여러 종류의 도형을 만드는 구조체를 가리키는 포인터가 된다.
id shape = shapesp[i];
[shape draw]
이 코드는 shapes 배열에서 id, 즉 객체를 가리키는 포인터를 얻어와서 id 라는 타입을 갖는 shape 이라는 변수에 넣는다. 이 작업은 단순히 포인터를 할당하는 것이며 실제로 도형의 모든 내용을 복사하지는 않는다.
[shape draw]
이 코드는 shape 이라는 이름의 객체에 draw 작업을 요청하고 있다. 만일 shape가 원인 경우 원이 그려진다. 사각형이면 사각형이 그려진다.
객체에 메시지를 보낼때는 클래스라고 하는 감춰진 도우미가 있어야 한다.
아래 그림에서 shapes 배열 0번째에 있는 인덱스에 있는 circle 객체를 보여준다. 이객체는 그 클래스를 가리키는 포인터를 가지고 있다. 클래스는 객체가 어떤 종류의 것인지를 알려준다. 아래 그림에서 Circle 클래스는 원을 그리라는 코드를 가리키는 포인터를 갖고 있으며, 원의 넓이를 계산하거나, Circle과 관련된 다른 내용을 가지고 있다.
클래스가 객체를 갖는다는 것은 어떤의미 일까? 각 객체가 코드를 직접 가리키고 있어 단지 구조가 간단해지는 것일까? 실제로 단순해 질 수 있으며, 어떤 OOP 시스템에서는 이런 방법을 사용하기도 한다. 그러나 클래스 객체를 갖는다는 것은 이외에 상당한 이득이 있다. 만일 런타임에 클래스 내용을 바꾼다면 그 클래스의 모든 객체는 자동으로 변경사항이 적용된다.
절차적 함수에서는 어떤 함수를 호출해야 할지 결정하는 코드를 작성해야 했다. 이제 그런 결정은 오브젝티브 씨에 의해서 원래 클래스의 객체에 요청함으로써 은밀하게 진행된다. 이런 방식은 잘못된 함수를 호출하는 실수를 줄이고 유지보수를 편하게 한다.
클래스 : 객체의 타입을 나타내는 구조체
객체 : 값과 클래스를 가리키는 숨어있는 포인터를 갖는다.
id 는 종류에 상관없이 객체를 가리키는 포인터다.
객체지향은 데이터가 우선이고 그 다음이 함수인 프로그래밍 스타일을 갖는다.
참고
drawShapes() 함수는 확장을 위해 열려있다 단지 그리려는 배열에 새로운 도형 객체만 추가하면 된다. 수정에는 닫혀 있다. 수정 없이도 확장할 수 있다. open/close 이론에 충실한 코드다.
ios와 맥 os x 개발을 위한 오브젝티브 -c 2판 도서 참고
'아이폰 개발 > Objective C' 카테고리의 다른 글
컴포지션 (0) | 2021.02.22 |
---|---|
상속 예제 (0) | 2021.02.01 |
objective c 프로토콜 3 - 비공식 프로토콜 (0) | 2020.12.31 |
objective c 프로토콜 2 - 프로토콜의 채용, 상속 카테고리화 (0) | 2020.12.31 |
objective c 프로토콜 1 (0) | 2020.12.31 |