스파르타 게임개발종합반(Unity)/기술 면접 대비 꾸준 실습

02. 객체지향 프로그래밍

테크러너 2024. 7. 3.

확인 문제

using System;

// 사각형 클래스
public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }

    public double CalculateArea()
    {
        return Width * Height;
    }
}

// 원 클래스
public class Circle
{
    public double Radius { get; set; }

    public double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

// 면적 계산기 클래스
public class AreaCalculator
{
    public double CalculateRectangleArea(Rectangle rectangle)
    {
        return rectangle.CalculateArea();
    }

    public double CalculateCircleArea(Circle circle)
    {
        return circle.CalculateArea();
    }
}

class Program
{
    static void Main(string[] args)
    {
        Rectangle rectangle = new Rectangle { Width = 5, Height = 10 };
        Circle circle = new Circle { Radius = 3 };

        AreaCalculator calculator = new AreaCalculator();

        double rectangleArea = calculator.CalculateRectangleArea(rectangle);
        double circleArea = calculator.CalculateCircleArea(circle);

        Console.WriteLine($"Rectangle Area: {rectangleArea}");
        Console.WriteLine($"Circle Area: {circleArea}");
    }
}

1. 위 코드에서 삼각형 Triangle 클래스를 새로 작성한다고 했을 때, AreaCalculator의 코드를 수정하지 않고 AreaCalculator가 삼각형의 넓이를 계산하도록 만들 수 있을까요?

AreaCalculator에 사각형 면적 계산이 있으므로, 사각형을 반으로 나누면 직각삼각형의 넓이를 계산할 수 있습니다.

 

2. 1번 질문 답변을 바탕으로 위 코드가 유지보수 측면이 어려운 부분을 설명해주세요.

1번 답변의 경우 직각삼각형만 가능하므로, 모든 삼각형의 면적을 구할 수 없기에 유지보수 측면에서 어렵습니다.

 

설명 문제

1. SOLID 원칙에 대해 설명해주세요.

차례대로 단일 책임 원칙 (Single Responsibility Principle, SRP) , 개방 폐쇄 원칙 (Open/Closed Principle, OCP) , 리스코프 치환 원칙 (Liskov Substitution Principle, LSP) , 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) , 의존 역전 원칙 (Dependency Inversion Principle, DIP) 을 의미합니다.

 

단일 책임 원칙은 클래스에 단 하나의 책임만 가져야 한다는 원칙입니다.

개방 폐쇄 원칙은 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙입니다.

리스코프 치환 원칙은 자식 클래스는 언제나 자신의 부모 클래스를 대체할 수 있어야 한다는 원칙입니다. 자식 클래스는 부모 클래스에서 정의된 기능을 무효화하거나 변경해서는 안 됩니다.

인터페이스 분리 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다. 인터페이스 분리 원칙은 큰 덩어리의 인터페이스들을 구체적이고, 작은 단위들로 분리시킴으로써 클라이언트들이 꼭 필요한 메서드들만 이용할 수 있도록 합니다.

의존 역전 원칙은 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다, 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다는 원칙입니다.

 

 

2. 객체지향 프로그래밍의 속성 중 하나인 다형성과 이를 활용한 설계의 장점에 대해 설명해주세요.

다형성은 하나의 인터페이스나 기능을 다양한 방식으로 구현하거나 사용할 수 있는 능력을 의미합니다.

하나의 메서드 이름이 다양한 객체에서 다르게 동작할 수 있도록 하는 것으로, 오버로딩과 오버라이딩을 통해 구현됩니다.

다형성의 장점은 유연하고 확장 가능한 코드 작성을 가능하게 하며, 코드의 가독성과 재사용성을 높입니다.

 

3. override와 overload에 대해 설명해주세요.

오버라이드( override )는 자식 클래스가 부모 클래스의 메서드를 재정의하는 것을 의미합니다. 자식 클래스는 부모 클래스의 메서드 시그니처(메서드 이름, 매개변수 목록, 반환 타입)를 동일하게 유지하면서, 메서드의 구체적인 구현을 변경합니다. 오버라이드를 사용하면 자식 클래스가 부모 클래스의 기능을 확장하거나 수정할 수 있습니다.

 

오버로드( overload )는 같은 이름의 메서드를 여러 개 정의하되, 서로 다른 매개변수 목록(매개변수의 수나 타입)을 가지도록 하는 것을 의미합니다. 이는 메서드가 서로 다른 인자들로 호출될 수 있도록 하며, 다양한 상황에 대응할 수 있게 합니다. 또한, 반환 타입만 다른 경우는 오버로드가 성립하지 않습니다.

 

잠깐! 여기서 의문이 들었던점 : 오버라이드는 부모 클래스의 메서드를 재정의 한다고 했다. 그러면 리스코프 치환 원칙에 위배되는거 아닐까? 올바른 예시와 잘못된 예시로 살펴보자.

 

올바른 예시 ▼

더보기

아래의 예시에서 두 클래스의 Fly 메서드의 동작은 일관성이 있다. 따라서 자식 클래스는 부모 클래스를 대체할 수 잇으며, 리스코프 치환 원칙을 준수한다.

public class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("Bird is flying");
    }
}

public class Sparrow : Bird
{
    public override void Fly()
    {
        Console.WriteLine("Sparrow is flying");
    }
}

잘못된 예시

더보기

아래의 예시에서 자식 클래스가 부모 클래스의 Fly 메서드를 오버라이드하여 예외를 던지게 한다. 이는 자식 클래스가 부모 클래스를 대체할 수 없게 만드는 것이므로 리스코프 치환 원칙에 위배된다.

public class Bird
{
    public virtual void Fly()
    {
        Console.WriteLine("Bird is flying");
    }
}

public class Penguin : Bird
{
    public override void Fly()
    {
        throw new NotSupportedException("Penguins cannot fly");
    }
}

 

4. 확장 메서드에 대해 설명하고 어떻게 활용했는지 알려주세요.

확장 메서드는 특수한 종류의 static 메서드인데, 확장메서드는 마치 다른 클래스(혹은 구조체)의 인스턴스 메서드인 것처럼 사용되는 기능을 제공합니다. 확장메서드는 클래스, 구조체, 인터페이스 등에 적용될 수 있습니다.

확장메서드는 static class 안에 static method로 정의됩니다. 첫번째 파라미터로 항상 클래스명(혹은 타입)을 지정하는데, 이는 그 확장메서드가 사용될 클래스 타입을 지정하는 것입니다. 이를 통해 확장메서드는 마치 그 클래스의 인스턴스 메서드인 것처럼 사용할 수 있게 됩니다. 약간 특이한 문법이지만, 확장 메서드의 첫번째 파라미터의 클래스명 바로 앞에는 항상 this를 써주도록 합니다.

 

 

실습 문제

💡 [Array에 기능 추가하기]

`Array` 클래스에 확장 메서드를 이용하여 배열의 평균값을 계산하는 기능을 추가하세요.

이 확장 메서드는 `CalculateAverage`라는 이름을 가지며, `int[]` 타입의 배열을 입력으로 받아 평균값을 `double` 타입으로 반환해야 합니다.

using System;

namespace ArrayExtensions
{
    public static class ArrayExtension
    {
        public static double CalculateAverage(this int[] array)
        {
            // TODO: 확장 메서드 CalculateAverage를 구현하세요
            
            //
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = { 1, 2, 3, 4, 5 };
            
            // 확장 메서드를 사용하여 배열의 평균값을 계산하고 출력
            double average = numbers.CalculateAverage();
            Console.WriteLine($"The average is: {average}");
        }
    }
}

 

답변 ▼

더보기
using System;

namespace ArrayExtensions
{
    public static class ArrayExtension
    {
        public static double CalculateAverage(this int[] array)
        {
            // TODO: 확장 메서드 CalculateAverage를 구현하세요
            int total = 0;
            foreach (var num in array)
            {
                total += num;
            }
            return total/array.Length;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = { 1, 2, 3, 4, 5 };

            // 확장 메서드를 사용하여 배열의 평균값을 계산하고 출력
            double average = numbers.CalculateAverage();
            Console.WriteLine($"The average is: {average}");
        }
    }
}
반응형

댓글