배열과 링크드리스트는 둘 다 컴퓨터 과학에서 사용하는 기본적인 데이터 구조입니다 .

데이터의 크기가 고정되어 있고, 특정 요소에 빠르게 접근해야 하는 경우에는 배열을 사용하는 것이 좋습니다.
반면에, 데이터의 크기가 동적으로 변하고, 요소의 추가와 삭제가 빈번한 경우에는 링크드리스트를 사용하는 것이 좋습니다.

 

배열과 링크드리스트는 데이터를 저장하는 방식이 다릅니다.

배열은:

  • 데이터를 연속적인 메모리 공간에 저장합니다. 즉, 한 줄로 나란히 놓는 것을 생각하시면 됩니다.
  • 각 데이터는 번호(인덱스)가 있어서 바로 접근할 수 있습니다.
  • 하지만 나머지 데이터를 이동시켜야 하기 때문에 크기를 변경하거나 중간에 데이터를 추가하거나 삭제하는 것은 번거롭습니다.
    • 배열은 고정된 크기를 가지며, 연속된 메모리 공간에 동일한 타입의 데이터를 저장합니다.
    • 배열의 각 요소는 인덱스를 통해 직접 접근할 수 있으므로, 특정 요소에 대한 조회는 O(1)의 시간 복잡도를 가집니다.
    • 하지만, 배열의 크기는 생성 시점에 결정되며 변경할 수 없기 때문에, 크기를 확장하거나 축소하려면 전체 배열을 복사해야 합니다. 이는 O(n)의 시간 복잡도를 가집니다.
    • 배열에 요소를 추가하거나 삭제하는 연산도 O(n)의 시간 복잡도를 가집니다. 왜냐하면 요소를 추가하거나 삭제한 후에 배열의 다른 요소들을 이동해야 하기 때문입니다.
public class ArrayExample {
    public static void main(String[] args) {
        // 크기가 5인 정수 배열 생성
        int[] array = new int[5];

        // 배열에 값 할당
        for(int i=0; i<array.length; i++) {
            array[i] = i * 2;
        }

        // 배열 출력
        for(int i=0; i<array.length; i++) {
            System.out.println("Element at index " + i + ": " + array[i]);
        }
    }
}

링크드리스트는:

  • 각 데이터(노드)가 다음 데이터를 가리키는 방식으로 저장합니다. 이런 노드들이 연결되어 있는 것을 생각하시면 됩니다.
  • 데이터를 추가하거나 삭제하는 것은 쉽지만, 특정 데이터를 찾으려면 처음부터 하나씩 따라가야 합니다.
  • 따라서 중간에 데이터를 추가하거나 삭제하는 경우에 유용합니다.
  • 링크드리스트는 동적인 크기를 가지며, 비연속적인 메모리 공간에 데이터를 저장합니다. 각 요소(노드)는 데이터와 다음 노드에 대한 참조를 가지고 있습니다.
  • 링크드리스트의 요소는 인덱스를 통해 직접 접근할 수 없으므로, 특정 요소에 대한 조회는 O(n)의 시간 복잡도를 가집니다. 왜냐하면 특정 요소를 찾기 위해 리스트를 순회해야 하기 때문입니다.
  • 하지만, 링크드리스트는 동적으로 크기를 변경할 수 있으므로, 요소를 추가하거나 삭제하는 연산은 O(1)의 시간 복잡도를 가질 수 있습니다. 하지만 이는 해당 위치의 참조가 이미 알려져 있을 때만 가능합니다. 그렇지 않은 경우에는 O(n)의 시간 복잡도를 가집니다.
import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        // LinkedList 생성
        LinkedList<String> linkedList = new LinkedList<>();

        // LinkedList에 요소 추가
        linkedList.add("Element 1");
        linkedList.add("Element 2");
        linkedList.add("Element 3");

        // LinkedList 출력
        for(int i=0; i<linkedList.size(); i++) {
            System.out.println("Element at index " + i + ": " + linkedList.get(i));
        }
    }
}

 

** 노드(Node)는 데이터를 담는 공간입니다.

예를 들어, 링크드리스트에서는 각 데이터가 저장되는 곳을 '노드'라고 부릅니다.

또한, 링크드리스트의 노드는 다음 노드를 가리키는 정보도 함께 저장하고 있습니다.

 

** O(n)의 시간 복잡도는 컴퓨터가 어떤 일을 하는데 걸리는 시간을 나타내는 표현 방식 중 하나입니다.

여기서 'n'은 데이터의 개수를 의미하고, O(n)은 '데이터 개수에 비례하여 시간이 걸린다'는 의미입니다.

예를 들어, 데이터가 10개 있을 때 10초가 걸리면, 데이터가 100개 있을 때는 100초가 걸린다는 뜻입니다.

간단히 말해, 배열에서는 어떤 데이터를 찾아가는데 한번에 있지만,

링크드리스트에서는 번째 데이터부터 하나씩 따라가며 찾아야 해서 시간이 걸린다는 것입니다.

때문에 링크드리스트에서 데이터를 찾는 시간을 O(n)으로 표현합니다.

 

**O(1)의 시간 복잡도는 어떤 작업을 수행하는 데 걸리는 시간이 데이터의 개수와 상관없이 항상 일정하다는 것을 의미합니다.

배열에서 특정 위치의 데이터에 바로 접근하는 것은 O(1)의 시간 복잡도를 가집니다.

왜냐하면 배열에서 각 데이터는 인덱스를 통해 바로 접근할 수 있기 때문입니다.

배열의 크기가 늘어나도, 특정 위치의 데이터에 접근하는데 걸리는 시간은 변하지 않습니다.


Array는 연속된 메모리 공간에 존재하고 Linked List는 메모리 상에서 떨어져 있는 데이터들이 앞의 데이터와 뒤의 데이터를 기억하는 형태로 존재한다. Array에 저장되어 있는 데이터를 조회할 때는 O(1)로 가능하지만 Linked List는 O(N)이 소요된다. Array에 데이터 추가 및 삭제할 때는 O(N)이 소요되지만 Linked List는 O(1)로 가능하다. 추가적으로 Array는 컴파일 과정에서 메모리가 할당되는 정적 메모리 할당인 반면 Linked List는 런타임 환경에서 메모리가 할당되는 동적 메모리 할당이다. 또한 배열은 Stack 영역에 메모리 할당이 되고, Linked List는 Heap 영역에 할당이 된다.

 

추가/삭제가 많다면 링크드리스트, 탐색/정렬이 많다면 배열을 사용하는 게 유리하다. 둘 다 선형 데이터 구조지만, 배열은 물리적으로도 연속된 메모리를 사용하고 링크드리스트는 다음 노드의 위치를 저장함으로써 흩어진 메모리를 연결해서 쓴다는 게 둘 사이의 차이점을 만드는 주요한 요인이다. 링크드리스트의 장점은 데이터의 추가/삽입 및 삭제가 용이하다. 즉, 새로운 노드를 끼워넣기 쉽다. 길이를 동적으로 조절 가능하다. 단점은 인덱스없이 연결관계만 있기 때문에 특정 노드를 불러내기 어렵다. (일반적으로) O(N)이 걸리며 순차적으로 탐색하게 되기 때문이다.(B+tree자료구조 등은 예외) 거꾸로 탐색하기도 어렵다. 정렬은 O(NlogN)이 걸린다. 배열의 장점은 자료마다 인덱스가 있어서 특정 자료를 불러내기 편하다. 단점은 연속된 메모리 공간을 할당받아야 하다보니 크기를 크게 키우기가 어렵다. 또한, 안 쓰는 공간까지 전부 예약해두고 있어야 하므로 공간 낭비가 생긴다.

 

배열은 입력된 데이터들이 메모리 공간에서 연속적으로 저장되어 있는 자료구조이며 메모리상에서 연속적으로 저장되어 있는 특징을 갖기 때문에 index를 통한 접근이 용이하다는 장점이 있으나 삽입/삭제가 오래 걸리고 배열 중간의 데이터가 삭제 되면 공간 낭비가 발생할 수 있는 단점이 존재합니다. 링크드리스트(연결리스트)는 여러 개의 노드들이 순차적으로 연결된 형태를 갖는 자료구조이며 각 노드는 데이터와 다음 노드를 가리키는 포인터로 이루어져 있는 트리(tree)구조의 근간이 되는 자료구조입니다. 배열과 달리 메모리를 연속적으로 사용하지 않아 삽입/삭제에 용이하다는 장점이 있습니다. 그러나 index로 임의 접근이 불가하며 처음부터 탐색을 해야하는 단점이 있습니다. 따라서 배열은 빠른 접근이 요구되고, 데이터의 삽입과 삭제가 적을 때 사용하고 링크드리스트는 삽입과 삭제 연산이 잦고, 검색 빈도가 적을 때 사용합니다.

 

** 배열,링크드리스트 사용 예시:

영화관의 좌석 배치의 경우 각 좌석은 행과 열에 따라 정확한 위치를 가지고 있습니다.

이러한 좌석 배치는 '배열'과 유사하게 생각할 수 있습니다.

좌석의 번호(예: A열 5번)를 알면, 해당 좌석을 즉시 찾아갈 수 있습니다. 이렇게 빠르게 접근이 가능한 것은 배열의 특징입니다.

하지만 한번 정해진 좌석 배치(배열의 크기)는 변경하기 어렵습니다.

즉, 중간에 좌석을 추가하거나 제거하기는 어렵다는 것이 배열의 단점입니다.


기차의 경우 각각의 (= 노드) 연결되어 있습니다.

칸은 다음 칸을 '가리키고' 있습니다. 이것이 바로 링크드리스트의 구조입니다.

기차는 필요에 따라 칸을 추가하거나 제거할 있습니다. 이렇게 동적으로 크기를 변경하는 것이 링크드리스트의 장점입니다.

하지만, 특정 칸에 바로 접근하려면 기차의 칸부터 시작해서 원하는 칸을 찾을 때까지 칸씩 이동해야 합니다.

이렇게 특정 위치에 접근하는 데에 시간이 걸리는 것이 링크드리스트의 단점입니다.

01. 두 수의 곱

나는 이렇게 풀었는데 다른사람의 풀이를 보니까 

이런 식으로 조건을 추가했는데 생각해보니까 int 타입의 변수때문에 조건을 추가해서 참일때만 수행하게 해야되는 것 같다. 

 

02. 몫 구하기

몫이랑 나머지 기억 안나서 % 로 했는데 ...

이것도 나누기하니까 타입을 추가해줘야 하는 거 같다... 디테일의 차이인가...\

 

03. 숫자 비교하기

다른사람의 풀이를 보는 것은 언제나 즐겁다...

나도 바로 리턴문으로 돌릴걸...

 

 

04. 두 수의 차

역시 디테일 장인들 ...

-50000 이상 50000 이하 제한 사항을 고려하여 그 외 숫자가 입력 시 연산이 안 되게 세팅.. 

 

05. 나머지 구하기

왜 나는 막상 문제를 풀면 어떻게 디테일을 추가해야 할지 모르겠지.... 

while 문은 잘 안써서 생각도 안하고 있었는데 뭔가 내 기준에서 신기한 풀이였다...

조건식이 참인 동안 계속 반복한다라...매우 수학적인 풀이다....

'Algorithm' 카테고리의 다른 글

프로그래머스 Lv.0  (0) 2023.05.23
프로그래머스 Lv.0  (0) 2023.05.20
프로그래머스 Lv.0  (2) 2023.05.19
프로그래머스 Lv.0  (0) 2023.05.19
프로그래머스 Lv.0  (1) 2023.05.18

** SOLID : 좋은 객체 지향 설계의 5가지 원칙 , 로버트마틴 클린코드 **

1. 단일 책임 원칙 (single responsibility principle,  SRP) :

하나의 클래스는 하나의 책임만 가져야 한다 

이때, 중요한 기준은 변경이다. 즉 변경이 있을 때 파급효과가 적은 경우 단일 책임 원칙을 잘 따른 것

 

2. 개방 폐쇄 원칙(Open closed principle, OCP)

확장에는 열려있으나 변경에는 닫혀있어야 한다, 다형성을 활용(인터페이스를 활용하여 새로운 클래스를 통한 새로운 기능을 구현)

문제점 : 구현객체를 변경하려면 클라이언트 코드를 변경해야한다

> 해당 문제점을 해결하기 위해 IoC 와 DI 같은 컨테이너가 필요하다 (객체를 생성하고 연관관계를 맺어주는 별도의 조립/설정자)

기능의 변경이나 추가가 발생해도 기존 코드는 수정되지 않고, 새로운 코드를 통해 기능이 확장되는 것이 이상적입니다.

 코드의 재사용성을 높이고유지 보수를 용이하게 만듭니다.

 

3. 리스코프 치환 원칙(Liskov substitution principle, LSP) : 

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위타입의 인스턴스로 바꿀 수 있어야 한다

즉, 다형성에서 하위클래스는 인터페이스의 규약을 다 지켜야 한다는 것 (컴파일 성공을 넘어서는 이야기이다)
서브타입은 언제나 기반 타입으로 대체될 수 있어야 한다는 원칙입니다. 즉, 자식 클래스는 부모 클래스의 역할을 완전히 대체할 수 있어야 합니다.

 

4. 인터페이스 분리 원칙(Interface segregation principle, ISP) : 

특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다,

분리하면 인터페이스 자체가 변해도 클라이언트에 영향을 주지 않음, 인터페이스도 명확하고 대체가능성이 높아진다. 
클라이언트는 자신이 사용하지 않는 인터페이스에 의존하게 만들어서는 안 된다는 원칙입니다. 이는 "작고 구체적인 인터페이스가 낫다"라는 원칙을 의미하며, 클래스는 자신이 필요로 하는 메서드만을 가진 인터페이스에 의존해야 함을 나타냅니다.

 

5. 의존관계 역전 원칙(Dependency inversion principle, DIP)

추상화에 의존하는 것이지 구체화에 의존하면 안된다, 구현클래스가 아닌 인터페이스에 의존한다는 뜻이다. 

클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다

정리 :

의존한다는 것은 내가 해당 클래스의 코드를 알고 있다는 뜻이다 . 

객체지향의 핵심은 다형성으로 다형성 만으로는 OCP,DIP를 지킬 수 없다. 

 

스프링은 DI(의존성 주입) + DI컨테이너의 제공(자바객체들을 컨테이너안에 넣어두고 연결,스프링 프레임워크)을 통해

다형성 + OCP<DIP를 가능하게 지원해준다 !! (클라이언트 코드의 변경 없이 기능확장)

 

하지만 실무에서는 인터페이스를 도입하면 "추상화"라는 비용이 발생한다

기능을 확장할 가능성이 없다면 구체 클래스를 직접 사용하고 향후 인터페이스를 도입(리팩터링)

언제나 장점이 단점을 넘어설때 선택하는 것이 중요

 

* 단일 책임 원칙에 대해 설명하고, 이 원칙을 지키지 않을 때 어떤 문제가 발생할 수 있는지 설명해주세요.

단일 책임 원칙(Single Responsibility Principle, SRP)은 객체 지향 프로그래밍에서 가장 기본적인 원칙 중 하나로, 한 클래스는 한 가지 책임만 가져야 함을 의미합니다. 이는 "변경의 이유는 한 가지만 있어야 한다"라는 원칙으로 이해될 수 있습니다.

단일 책임 원칙을 지키면 코드의 응집도를 높이고 결합도를 낮출 수 있어, 유지 보수성이 향상되고 테스트하기가 더 쉬워집니다. 또한, 각 클래스의 역할이 명확해지므로 코드를 이해하고 사용하는데 도움이 됩니다.

반면에 이 원칙을 지키지 않으면 다음과 같은 문제가 발생할 수 있습니다:

  1. 유지 보수성 감소: 한 클래스가 너무 많은 책임을 지게 되면, 한 책임에 대한 변경이 다른 책임에 미치는 영향을 예측하기 어렵게 됩니다. 결과적으로 코드 변경이 어렵고 위험해집니다.
  2. 테스트 어려움: 여러 책임을 가진 클래스는 테스트하기가 어려워집니다. 한 책임에 대한 테스트를 하려면 다른 책임과 관련된 코드도 함께 실행되기 때문입니다.
  3. 코드 이해도 낮아짐: 클래스의 책임이 많아지면 클래스의 역할을 이해하고 사용하기가 어려워집니다. 이는 특히 다른 개발자가 코드를 이해하고 수정하는 데 문제가 될 수 있습니다.

따라서, 클래스가 단일 책임을 가지도록 설계하는 것은 좋은 객체 지향 설계를 위한 중요한 원칙입니다.

 

* 개방-폐쇄 원칙의 의미와 이 원칙을 따르는 설계의 장점에 대해 설명해주세요.

개방-폐쇄 원칙(Open-Closed Principle, OCP)은 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다는 원칙입니다. 즉, 기능을 변경하거나 확장해야 할 때 기존의 코드는 수정하지 않고, 새로운 코드를 추가함으로써 기능의 변경이나 확장이 이루어져야 합니다.

이 원칙을 따르는 설계의 장점은 다음과 같습니다:

  1. 유지 보수성 향상: 기존 코드를 수정하지 않으므로, 기존 코드가 잘 작동하는 한 새로운 기능 추가나 변경이 기존 기능에 부정적인 영향을 미치지 않습니다. 따라서 버그의 발생 가능성이 줄어들고, 코드의 안정성이 높아집니다.
  2. 확장성 향상: 새로운 기능을 추가하거나 기존 기능을 변경해야 할 때, 기존 코드는 그대로 두고 새로운 코드를 추가하는 방식이므로, 시스템의 확장성이 좋아집니다. 이는 빠르게 변하는 요구사항에 효과적으로 대응할 수 있게 해줍니다.
  3. 재사용성 증가: 기존의 코드를 변경하지 않고도 새로운 기능을 추가할 수 있으므로, 기존 코드나 모듈을 재사용하기 쉽습니다. 이는 개발 시간을 단축시키고, 코드의 중복을 줄여주며, 개발 생산성을 향상시킵니다.

개방-폐쇄 원칙은 추상화와 다형성을 활용하여 구현할 수 있습니다. 예를 들어, 추상 클래스나 인터페이스를 정의하고, 이를 구현한 구체 클래스를 확장하여 새로운 기능을 추가하는 방식입니다. 이렇게 하면 기존 코드를 변경하지 않고도 새로운 기능을 추가하거나 기존 기능을 변경할 수 있습니다.

 

* 리스코프 치환 원칙을 위반했을 때 어떤 문제가 발생하는지 예를 들어 설명해주세요.

리스코프 치환 원칙(Liskov Substitution Principle, LSP)은 서브타입은 언제나 기반 타입으로 대체될 수 있어야 함을 의미합니다. 즉, 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 대체해도 프로그램이 제대로 작동해야 합니다.

이 원칙을 위반하면 다음과 같은 문제가 발생할 수 있습니다:

예를 들어, "사각형" 클래스가 "너비"와 "높이"라는 속성을 가지고, "넓이를 계산하는 메서드"를 가지고 있다고 생각해봅시다. 이 클래스를 상속받아 "정사각형" 클래스를 만든다면, "정사각형"은 "너비"와 "높이"가 항상 같아야 하므로, "너비"나 "높이"를 설정하는 메서드가 호출될 때 다른 하나의 속성도 같이 변경되어야 합니다.

그런데 만약 "사각형" 클래스의 인스턴스를 "정사각형" 클래스의 인스턴스로 대체해 사용하려고 하면 문제가 발생합니다. 예를 들어, "너비"와 "높이"를 각각 설정한 후 "넓이를 계산하는 메서드"를 호출하면, "사각형"에서는 기대한 결과를 얻을 수 있지만, "정사각형"에서는 기대한 결과를 얻지 못하게 됩니다.

, 리스코프 치환 원칙을 위반하면, 부모 클래스와 자식 클래스가 제대로 대체관계를 가지지 못하게 되어, 코드의 예측 가능성과 재사용성이 떨어지며, 버그의 발생 가능성이 높아집니다. 이런 문제를 피하기 위해, 상속을 사용할 때는 리스코프 치환 원칙을 항상 고려해야 합니다.

 

* "Liskov Substitution Principle"은 Method Overriding과 어떤 관련이 있나요?

메서드 오버라이딩은 자식 클래스에서 부모 클래스의 메서드를 재정의하는 것입니다. 만약 리스코프 치환 원칙을 위반하게 되면, 메서드 오버라이딩이 문제를 일으킬 수 있습니다. 예를 들어, 부모 클래스의 메서드와 동일한 이름과 시그니처를 가진 메서드를 자식 클래스에서 오버라이딩하되, 그 기능이나 동작 방식, 반환 값 등이 부모 클래스의 메서드와 많이 다르다면, 부모 클래스의 인스턴스가 들어갈 자리에 자식 클래스의 인스턴스가 들어가게 되면 프로그램이 예상대로 동작하지 않게 됩니다.

따라서, 메서드를 오버라이딩할 때는 리스코프 치환 원칙을 항상 염두에 두고, 부모 클래스의 메서드를 자식 클래스에서 재정의하더라도 프로그램의 동작에 문제가 없도록 해야 합니다.

 

* 인터페이스 분리 원칙을 위반하면 어떤 문제가 생길 수 있는지 설명해주세요.

인터페이스 분리 원칙(Interface Segregation Principle, ISP)은 클라이언트가 사용하지 않는 메서드에 의존하지 않도록, 즉 "클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다"는 원칙입니다. 이는 클라이언트 관점에서 필요한 메서드만을 제공하는 것이 중요하다는 의미입니다.

이 원칙을 위반하면 다음과 같은 문제가 발생할 수 있습니다:

  1. 낮은 응집력: 클라이언트가 사용하지 않는 메서드에도 의존하게 되므로 클래스나 모듈의 응집력이 낮아집니다. 응집력이 낮아지면 코드의 이해도가 떨어지고, 유지보수가 어려워집니다.
  2. 불필요한 의존성: 클라이언트는 필요하지 않은 메서드에 대해서도 의존성을 가지게 됩니다. 이로 인해 클라이언트는 자신이 사용하지 않는 메서드에 대한 변경사항에도 영향을 받게 될 수 있습니다.
  3. 낮은 재사용성: 인터페이스가 과도하게 크고 복잡해지면 재사용성이 떨어집니다. 특정 클라이언트에 맞춰진 크고 복잡한 인터페이스는 다른 클라이언트에서는 필요하지 않은 메서드들을 포함하게 될 수 있습니다.

따라서 인터페이스는 최대한 작게 나누고, 특정 목적에 맞게 분리하는 것이 좋습니다. 이렇게 하면 클라이언트는 자신이 필요로 하는 메서드만을 제공하는 인터페이스에 의존하게 되므로, 불필요한 의존성을 줄이고 코드의 유지보수성과 재사용성을 높일 수 있습니다.

 

* 의존성 역전 원칙에 대해 설명하고, 이 원칙이 설계에 어떤 영향을 미치는지 설명해주세요.

의존성 역전 원칙(Dependency Inversion Principle, DIP)은 고수준 모듈이 저수준 모듈에 의존하면 안 되며, 둘 다 추상화에 의존해야 한다는 원칙입니다. 또한, 추상화는 구체적인 사항에 의존하면 안 되며, 구체적인 사항이 추상화에 의존해야 합니다.

즉, 소프트웨어 컴포넌트 간의 의존관계가 하위 레벨에서 상위 레벨로 가는 것이 아니라, 상위 레벨의 추상화된 인터페이스를 향해 가야 함을 의미합니다.

이 원칙이 설계에 미치는 영향은 다음과 같습니다:

  1. 모듈 간의 느슨한 결합: DIP를 따르면 고수준 모듈과 저수준 모듈 사이의 의존성이 줄어들고, 이들은 추상화된 인터페이스에 의존하게 됩니다. 이로 인해 시스템의 각 부분 사이의 결합이 느슨해지며, 변경에 대한 영향이 줄어듭니다.
  2. 유지 보수성과 확장성 향상: 추상화에 의존함으로써 코드 변경이나 기능 추가가 상대적으로 용이해집니다. 하위 모듈이 변경되거나 새로운 하위 모듈이 추가되더라도, 상위 모듈은 변경되지 않거나 최소한의 변경만으로도 대응할 수 있습니다.
  3. 재사용성 증가: 고수준 모듈과 저수준 모듈이 추상화에 의존하게 되므로, 각 모듈이 독립적이고 재사용 가능해집니다. 따라서 코드의 재사용성이 증가하게 됩니다.

DIP 지키지 않으면, 하위 레벨의 모듈 변경이 상위 레벨의 모듈까지 영향을 미칠 있어 시스템이 불안정해질 있으며, 유지보수와 확장이 어려워질 있습니다. 원칙은 특히 대규모 시스템에서 중요한데, 이를 지키면 시스템의 다른 부분에 영향을 주지 않고 특정 부분을 변경하거나 확장할 있기 때문입니다.

 

* 실제 작업에서 SOLID 원칙을 어떻게 적용하고 있나요?

실제 작업에서 SOLID 원칙은 코드의 품질을 높이고 유지 보수를 쉽게 만드는 데에 도움을 줍니다. 각 원칙은 다음과 같이 적용될 수 있습니다:

  1. 단일 책임 원칙 (Single Responsibility Principle, SRP): 하나의 클래스나 함수는 하나의 책임만을 가져야 합니다. 원칙을 적용함으로써, 하나의 변경이 다른 기능에 영향을 미치는 것을 최소화하고 코드를 이해하고 유지 관리하기 쉽게 만들 있습니다.
  2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP): 기존의 코드를 변경하지 않고 기능을 추가하거나 변경할 있도록 설계해야 합니다. 원칙은 주로 상속과 인터페이스를 통해 구현됩니다. 원칙을 지키면 기존 코드에 영향을 미치지 않고 새로운 기능을 추가할 있으므로 유지 보수가 용이해집니다.
  3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP): 하위 클래스는 상위 클래스를 대체할 있어야 합니다. 원칙은 주로 메서드 오버라이딩과 관련이 있습니다. 원칙을 지키면 클래스의 일관성이 유지되고 코드의 예측 가능성이 높아집니다.
  4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP): 클래스는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 합니다. 원칙을 지키면 필요 없는 의존성을 줄이고 모듈 간의 결합도를 낮출 있습니다.
  5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP): 고수준 모듈은 저수준 모듈에 직접 의존하는 대신 추상화에 의존해야 합니다. 원칙을 지키면 코드의 유연성과 재사용성이 증가하고 모듈 간의 결합도가 낮아집니다.

* SOLID 원칙을 지키는 것이 중요하다고 생각하는 이유는 무엇인가요?

SOLID 원칙을 지키는 것이 중요한 이유는 크게 세 가지로 요약할 수 있습니다:

  1. 유지 보수성: SOLID 원칙을 따르면 코드의 유지 보수가 용이해집니다. 각 원칙이 코드의 결합도를 낮추고 응집력을 높여서, 변경이나 추가 기능이 필요할 때 그 영향을 최소화하게 합니다. 이는 유지 보수 비용을 줄이는 데 도움이 됩니다.
  2. 확장성: 개방-폐쇄 원칙과 의존성 역전 원칙 같은 SOLID 원칙은 코드의 확장성을 높입니다. 새로운 요구 사항이나 기능 추가가 필요할 때, 기존 코드를 크게 변경하지 않고도 새로운 기능을 추가하거나 기존 기능을 수정할 수 있게 됩니다.
  3. 재사용성: SOLID 원칙을 따르면 코드의 재사용성이 높아집니다. 각각의 클래스와 모듈이 잘 정의된 책임을 갖게 되어, 필요할 때 다른 곳에서 재사용할 수 있게 됩니다.

위와 같이, SOLID 원칙은 개발자가 효율적이고 유지 보수가 쉬운, 확장 가능하며 재사용 가능한 코드를 작성하는 도움이 됩니다. 그러나 모든 상황에 SOLID 원칙을 무조건 적용하는 것이 아니라, 상황에 맞게 적절하게 적용해야 합니다.

 

* SOLID 원칙을 준수하면서 겪었던 어려움은 무엇이었나요?

  1. 설계 복잡성: SOLID 원칙을 준수하려면 종종 추가적인 추상화 레벨이나 디자인 패턴이 필요합니다. 이로 인해 설계가 복잡해질 있습니다. 예를 들어, 의존성 역전 원칙을 준수하기 위해 인터페이스나 추상 클래스를 도입하면, 코드의 복잡성이 증가할 있습니다.
  2. 이해도와 학습 곡선: SOLID 원칙을 처음 배우고 이해하는 것은 쉽지 않을 있습니다. 원칙이 의미하는 바와 원칙을 지키기 위한 방법을 이해하는 데는 시간이 필요하며, 이는 개발 과정에 추가적인 시간을 요구합니다.
  3. 과도한 적용: 때로는 SOLID 원칙을 과도하게 적용하는 경향이 있을 있습니다. 모든 클래스나 모듈이 단일 책임 원칙을 완벽하게 지키려다 보면, 과도한 클래스 분리가 일어나서 오히려 코드를 이해하고 유지 보수하는 어려움을 초래할 있습니다.
  4. 성능 문제: SOLID 원칙의 적용은 때때로 성능에 부정적인 영향을 미칠 있습니다. 예를 들어, 인터페이스를 통한 의존성 역전은 런타임에 객체 생성과 가비지 컬렉션 비용을 증가시킬 있습니다.

* SOLID 원칙과 디자인 패턴 사이의 관계에 대해 설명해주세요.

SOLID 원칙과 디자인 패턴은 모두 좋은 소프트웨어 설계를 위한 지침입니다. 그러나 그들은 다른 목적과 적용 범위를 가지고 있습니다.

SOLID 원칙은 객체 지향 프로그래밍의 핵심 원칙들을 총체적으로 설명한 것입니다. 이 원칙들은 클래스와 객체, 그리고 이들 간의 관계를 어떻게 설계해야 하는지에 대한 가이드라인을 제공합니다. SOLID 원칙을 따르면 느슨한 결합, 높은 응집력, 재사용 가능성, 유지 보수성 등을 달성할 수 있습니다.

한편, 디자인 패턴은 특정 문제에 대한 해결책을 제공합니다. 이는 재사용 가능한 설계를 만드는 데 도움이 됩니다. 디자인 패턴은 일반적으로 소프트웨어 개발에서 반복적으로 발생하는 문제를 해결하는 방법을 제공합니다.

SOLID 원칙과 디자인 패턴은 서로 상호 보완적인 관계에 있습니다. 디자인 패턴은 대부분 SOLID 원칙을 따르며, SOLID 원칙을 이해하고 적용하면 디자인 패턴을 더 잘 이해하고 적용할 수 있습니다. 예를 들어, "전략 패턴"은 개방-폐쇄 원칙을 따르며, "팩토리 패턴"은 의존성 역전 원칙을 따릅니다.

따라서 SOLID 원칙과 디자인 패턴은 각각 다른 측면에서 소프트웨어 설계의 품질을 향상시키는 도구로 볼 수 있으며, 이 두 가지를 함께 사용하면 더욱 견고하고 유지 보수가 용이한 코드를 작성하는 데 도움이 될 수 있습니다.

 

더보기
  1. 전략 패턴과 개방-폐쇄 원칙(OCP)
    전략 패턴은 알고리즘 또는 정책을 캡슐화하는 행위 디자인 패턴입니다. 패턴은 알고리즘을 별도의 클래스로 정의하고, 공통 인터페이스를 갖게 하여 상호 교환 가능하게 만듭니다. 이로 인해 알고리즘을 독립적으로 변경하거나 확장할 있게 되며, 이는 바로 개방-폐쇄 원칙(OCP) 표현입니다.
    개방-폐쇄 원칙은 "소프트웨어 구성요소(클래스, 모듈, 함수 등등) 확장에는 열려 있고, 변경에는 닫혀 있어야 한다."라는 원칙입니다. 이를 통해 새로운 기능을 추가하더라도 기존 시스템의 수정 없이 확장이 가능하게 됩니다. 전략 패턴은 원칙을 구현하며, 알고리즘의 변화에 따라 기존 클래스를 수정하지 않고, 새로운 알고리즘을 추가하여 확장할 있게 합니다.
  2. 팩토리 패턴과 의존성 역전 원칙(DIP)
    팩토리 패턴은 객체 생성 로직을 캡슐화하는 생성 패턴입니다. 패턴은 클라이언트가 직접 객체를 생성하지 않고, 팩토리 클래스를 통해 객체를 생성합니다. 이는 클라이언트와 구체적인 객체 생성 클래스 사이의 의존성을 줄여줍니다.
    의존성 역전 원칙은 "고수준 모듈은 저수준 모듈에 의존하면 되고, 추상화에 의존해야 한다."라는 원칙입니다. , 구체적인 클래스에 의존하는 것이 아니라, 인터페이스나 추상 클래스에 의존하게 하는 것입니다.
    팩토리 패턴에서는 객체 생성을 담당하는 팩토리 클래스를 통해 원칙을 구현합니다. 클라이언트는 구체적인 클래스를 직접 참조하지 않고, 팩토리 클래스에 객체 생성을 요청합니다. 이로 인해 객체 생성을 위해 클라이언트가 구체적인 클래스에 의존하지 않게 되며, 의존성이 역전되는 것입니다.