[11.21] 내일배움캠프[Spring] TIL-15

박상훈·2022년 11월 21일
0

내일배움캠프[TIL]

목록 보기
15/72

[11.21] 내일배움캠프[Spring] TIL-15

1. Java - SOLID

  • SOLID

1) SRP : 단일 책임 원칙
👉 한 클래스는 하나의 책임만 가져야한다.
👉 하나의 책임은 클수도 있고, 작을 수도 있다.
👉 하나의 책임의 기준은 변경이다.
👉 변경이 발생하였을 때, 변경해야 될 부분이 적으면, 단일책임 원칙을 잘 따른 것이다.
👉 클래스를 변경해야하는 이유가 오직 하나여야 한다.
👉 ex) 결제 버튼의 위치가 변경되었지만, 결제 기능에 대한 영향은 없다.

  • 코드로 보는 단일 책임 원칙 예시
class Galaxy {
  private String serialNumber;
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}

--------------
serialNumber는 고유정보 -> 변화 요소가 아님.
cpu, memory, battery, weight는 특성 정보로 변경이 발생할 수 있음.
특성 정보에 변화가 발생하면, Galaxy 을 변화 시켜야 되는 부담이 발생함.

-------------------------
// 스펙만 관리
class GalaxySpec {
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}

class Galaxy {
   private String serialNumber;
	 private GalaxySpec spec;
   public Galaxy(String serialNumber, GalaxySpec spec) {
      this.serialNumber = serialNumber;
      this.spec = spec;
   }
}

2) OCP : 개방/폐쇠 원칙
👉 확장에는 열려있으나, 변경에는 닫혀 있어야한다.
👉 변경을 위한 비용은 줄이고, 확장을 위한 비용은 극대화 해야한다.
👉 객체지향의 장점을 극대화하는 아주 유용한 원리( 다형성 )
👉 템플릿 메서드 패턴 -> 개방/폐쇠 원칙의 아주 좋은 예시

  • 코드로 보는 개방/폐쇠 원칙 예시
class Galaxy {
  private String serialNumber;
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}
class GalaxySpec {
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}

class Galaxy {
   private String serialNumber;
	 private GalaxySpec spec;
   public Galaxy(String serialNumber, GalaxySpec spec) {
      this.serialNumber = serialNumber;
      this.spec = spec;
   }
}

----------

class IPhone {
  private String serialNumber;
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}
class IPhoneSpec {
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}

class IPhone {
   private String serialNumber;
	 private IPhoneSpec spec;
   public IPhone(String serialNumber, IPhoneSpec spec) {
      this.serialNumber = serialNumber;
      this.spec = spec;
   }
}

class Shaomi {
  private String serialNumber;
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}
class ShaomiSpec {
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}

class Shaomi {
   private String serialNumber;
	 private ShaomiSpec spec;
   public IPhone(String serialNumber, IPhoneSpec spec) {
      this.serialNumber = serialNumber;
      this.spec = spec;
   }
}

-------------------------------------------------
갤럭시 외에 새로운 핸드폰들이 생겨난다면?

추상화 작업을 통해서, 공통화 하자.

새로운 핸드폰이 추가 되면서, 변경 될 수 있는 부분들을 분리시킴으로써, 코드 수정을 최소화하여,
결합도를 줄이고,응집도를 높혔다.

-------------------------------------------------

class Phone {
   private String serialNumber;
	 private PhoneSpec spec;
   public Phone(String serialNumber, PhoneSpec spec) {
      this.serialNumber = serialNumber;
      this.spec = spec;
   }
}

class PhoneSpec {
  private String cpu;
  private String memory;
  private int battery;
  private double weight;
}

class Galaxy extends Phone

class IPhone extends Phone

class 샤오미 extends Phone

class Sony extends Phone

3) LSP : 리스코프 치환 원칙
👉 하위타입(자식)은 언제나 상위 타입(부모)로 교체될 수 있어야한다.
👉 정확성을 깨뜨리면 안된다.
👉 원칙을 지키지 않으면, 자식 클래스의 인스턴스를 파라미터로 전달했을 때 메소드가 이상하게 작동할 수 있다.
👉 ex) 어떤 경우라도 정해진 기능을 수행할 것...!

  • 코드로 보는 리스코프 치환 원칙 예시
class Rectangle {
    private int width;
    private int height;

    public void setHeight(int height) {
        this.height = height;
    }

    public int getHeight() {
        return this.height;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getWidth() {
        return this.width;
    }

    public int getArea() {
        return this.width * this.height;
    }
}

class Square extends Rectangle {
    @Override
    public void setHeight(int value) {
        this.width = value;
        this.height = value;
    }

    @Override
    public void setWidth(int value) {
        this.width = value;
        this.height = value;
    }
}

public class Test {
   static void testLSP(Rectangle rectangle) {
         rectangle.setWidth(5);
         rectangle.setHeight(3);
         System.out.println(rectangle.getArea());
   }
}

public static void main(String[] args){
      Rectangle rectangle = new Rectangle();
      rectangle.setWidth(5);
      rectangle.setHeight(2);
      rectangle.getArea(); // 10

      Rectangle square = new Square();
      square.setWidth(5);
      square.setHeight(2);
      square.getArea(); // 25
      
      Test.testLSP(new Rectangle());
      Test.testLSP(new Square());
}

4) ISP : 인터페이스 분리 원칙
👉 클라이언트가 자신이 사용하지 않는 메서드에 의존하지 않아야한다.
👉 특정 클라이언트를 위하여, 하나의 범용 인터페이스를 제공하는 것 보다 여러개의 인터페이스를 제공하는 것이 더 좋다.

  • 코드로 보는 인터페이스 분리 원칙 예시
public interface Phone {
    void call(String phoneNumber);
    void pay(String cardName);
    void wirelessCharge();
}

public class Galaxy implements Phone {
    @Override
    public void call(String phoneNumber) {
         // ..
    }

    @Override
    public void pay(String cardName) {
        // ..
    }

    @Override
    public void wirelessCharge() {
        // ..
    }
}

public class IPhone implements Phone {
    @Override
    public void call(String phoneNumber) {
         // ..
    }

    @Override
    public void pay(String cardName) {
        // ..
    }

		@Override
    public void wirelessCharge() {
        // ..
    }
}

public class SkyPhone implements Phone {
    @Override
    public void call(String phoneNumber) {
         // ..
    }

    @Override
    public void pay(String cardName) {
        // ..
    }

		@Override
    public void wirelessCharge() {
         // 1. 가만히 냅눈다. - 극강 빌런
         // 2. System.out.println("사용하지 않음"); - 그나마 양호
         // 3. throw new NotSupporedException(); - 테스트코드에서
    }

}


public void main (String args[]) {

   Phone phone = new Galaxy();
   phone = new Galaxy();
   phone = new SkyPhone();



   
   phone.wirelessCharge();  // 1. 에러를 찾는데, 시간이 오래 걸린다. 이건 빌런의 소행이다.
}

5) DIP : 의존관계 역전 원칙
👉 객체들 간의 협력 하는 과정에서 의존 관계가 형성된다.
👉 의존관계 역전 원칙은 이러한 의존 관계를 맺을 때, 어떻게 하면 변화에 용이하게 대응할 수 있을 것인가에 대한 가이드 라인이다.
👉 변하기 쉬운 것과 어려운 것을 구분해야한다.
👉 변하기 쉬운 것 -> 구체적인 행동(ex. 스마트폰으로 전화를 건다, 공중전화로 전화를 건다...)
👉 변하기 어려운 것 -> 흐름이나 개념 같은 추상적인 것 (ex. 전화를 건다, 메세지를 전달한다...)

  • 코드로 보는 의존관계 역전 법칙 예시
public interface Phone {
   void call(String phoneNumber);
}

public class SmartPhone implements Phone {
    @Override
    public void call(String phoneNumber) {
        System.out.println("스마트폰 : " + phoneNumber);
    }
}

public class PublicPhone implements Phone {
    @Override
    public void call(String phoneNumber) {
        System.out.println("공중 전화 : " + phoneNumber);
    }
}

public class InternetPhone implements Phone {
    @Override
    public void call(String phoneNumber) {
        System.out.println("인터넷 전화 : " + phoneNumber);
    }
}


public class Person {
  private Phone phone;
  
  public void setPhone(Phone phone) {
      this.phone = phone; // 폰이 계속 바뀜.
  }

  public void call(String phoneNumber) {
      phone.call(phoneNumber);
  }
}

public class Main {
	public static void main(String[] args) {
				Person person = new Person();
        person.setPhone(new SmartPhone());
        person.setPhone(new InternetPhone());



        // 코드 수정 X
        person.call("01012341234"); // 스마트폰 전화,
  }
}

2. Java 코테

👉 입력 받은 수의 약수 합 구하기 예제( 매우 쉬움 난이도 )

class Solution {
    public int solution(int n) {

        // 정수 n을 입력받아 n의 약수를 모두 더한 값을 리턴하는 함수 완성해라
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            if (n % i == 0) {
                sum += i;
            }
        }

        return sum;
    }
}

👉 설문조사에 따른 성격유형 검사( 문제가 길어서 오래걸렸음... 그리고 매우 비효율적으로 푼듯..)

  • 내가 짠 아주 비효율적 코드...
    👉 생각의 흐름: 배열방에 성격 유형을 판단할 알파벳을 미리 넣어놓은 배열방과 점수를 받을 배열 방을 미리 만들어 놓고 어차피 index는 같기 때문에 설문조사에 따른 점수를 저장하고, 비교하면서 출력하면 되지 않을까...
import java.lang.reflect.Array;

class Solution {
    public String solution(String[] survey, int[] choices) {

        // survey = 설문지의 문항 갯수
        // choices = 1 ~ 7 가지 선택지 중 고른 번호


        char [] arr = {'R','T','C','F','J','M','A','N'};
        int [] arr_count = {0,0,0,0,0,0,0,0};
        String answer = "";

        for(int i =0;i<survey.length;i++){
                int f_index = -1;
                int b_index = -1;
            for(int j=0;j<arr.length;j++){

                char front = survey[i].charAt(0);
                char back = survey[i].charAt(1);

                 if (front == arr[j]) {
                    f_index = j;
                }else if(back == arr[j]){
                    b_index = j;
                }

                if(f_index >= 0 && b_index >= 0) {

                    switch (choices[i]) {
                        case 1:
                            arr_count[f_index] += 3;
                            break;
                        case 2:
                            arr_count[f_index] += 2;
                            break;
                        case 3:
                            arr_count[f_index] += 1;
                            break;
                        case 5:
                            arr_count[b_index] += 1;
                            break;
                        case 6:
                            arr_count[b_index] += 2;
                            break;
                        case 7:
                            arr_count[b_index] += 3;
                            break;
                        default:
                            break;


                    }
                }

            }

        }

        for(int k = 0;k<arr_count.length;){

            if (arr_count[k] > arr_count[k+1]){
                answer+=arr[k];
            }else if(arr_count[k] < arr_count[k+1]){
                answer+=arr[k+1];
            }else{
                answer+=arr[k];
            }
            k+=2;


        }
        return answer;
    }


    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.solution(new String[]{"AN", "CF", "MJ", "RT", "NA"}, new int[]{5, 3, 2, 7, 5}));
    }

}
import java.util.*;

class Solution {
    public String solution(String[] survey, int[] choices) {
        Map<Character, Integer> map = new HashMap<>();

        for(int i = 0; i< survey.length; i++) {
            int value = choices[i];

            if(value > 0 && value < 4) {
                char ch = survey[i].charAt(0);
                map.put(ch, map.getOrDefault(ch, 0) + 4 - value);
            } else if(value > 4) {
                char ch = survey[i].charAt(1);
                map.put(ch, map.getOrDefault(ch, 0) + value - 4);
            }

        }

        return new StringBuilder()
            .append(map.getOrDefault('R', 0) >= map.getOrDefault('T', 0) ? 'R' : 'T')
            .append(map.getOrDefault('C', 0) >= map.getOrDefault('F', 0) ? 'C' : 'F')
            .append(map.getOrDefault('J', 0) >= map.getOrDefault('M', 0) ? 'J' : 'M')
            .append(map.getOrDefault('A', 0) >= map.getOrDefault('N', 0) ? 'A' : 'N')
            .toString();
    }
}

👉 Map 자료형을 사용하여 내가 사용했던 count를 위한 정수 배열의 필요성을 없앴다.
👉 Map자료형은 검색에 적합하기 때문에 훨씬 적합.. 편리..효율...!
👉 나는 String자료형도 이어줄 때 + 사용.. 매우 비효율적..
👉 StringBuilder()를 사용하여 String 클레스 객체를 한번만 사용하여 문자열을 이어줌..

3. 파이썬 알고리즘

1) 문자열 압축예제
👉 일정 반복되는 문자열을 갯수+반복알파벳 으로 묶어 문자열을 압축하자

input = "abcabcabcabcdededededede"


# 모든 경우의 수를 파악하는 방법이라고 보면 되겠다.
# n//2 -> 반절을 넘었을 때 반복될 수 없다.
def string_compression(string):
    n = len(string)
    compression_length_array = []
    for split_size in range(1, n // 2 + 1):
        splited = [string[i:i + split_size] for i in range(0, n, split_size)]
        compressed = ""
        count = 1

        for j in range(1, len(splited)):
            prev, cur = splited[j - 1], splited[j]

            if prev == cur:
                count += 1
            else:
                if count > 1:
                    compressed += (str(count) + prev)
                else:
                    compressed += prev
                count = 1

        if count > 1:
            compressed += (str(count) + splited[-1])
        else:
            compressed += prev
        compression_length_array.append(len(compressed))

    return min(compression_length_array)


print(string_compression(input))  # 14 가 출력되어야 합니다!

2) 괄호 배열 맞추기 예제

from collections import deque

balanced_parentheses_string = "()))((()"


def is_correct_parentheses(string):  # 올바른 괄호인지 확인
    stack = []
    for s in string:
        if s == '(':
            stack.append(s)
        elif stack:
            stack.pop()
    return len(stack) == 0


def separate_to_u_v(string):  # u, v로 분리
    queue = deque(string)
    left, right = 0, 0
    u, v = "", ""

    while queue:  # 큐사용
        char = queue.popleft()
        u += char
        if char == '(':
            left += 1
        else:
            right += 1
        if left == right:  # 단, u는 "균형잡힌 괄호 문자열"로 더 이상 분리할 수 없어야 합니다. 즉, 여기서 괄 쌍이 더 생기면 안됩니다.
            break

    v = ''.join(list(queue))
    return u, v


def reverse_parentheses(string):  # 뒤집기
    reversed_string = ""
    for char in string:
        if char == '(':
            reversed_string += ")"
        else:
            reversed_string += "("
    return reversed_string


def change_to_correct_parentheses(string):
    if string == '':  # 1번
        return ''
    u, v = separate_to_u_v(string)  # 2번
    if is_correct_parentheses(u):  # 3번
        return u + change_to_correct_parentheses(v)
    else:  # 4번
        return '(' + change_to_correct_parentheses(v) + ')' + reverse_parentheses(u[1:-1])


def get_correct_parentheses(balanced_parentheses_string):
    if is_correct_parentheses(balanced_parentheses_string):
        return balanced_parentheses_string
    else:
        return change_to_correct_parentheses(balanced_parentheses_string)


print(get_correct_parentheses(balanced_parentheses_string))  # "()(())()"가 반환 되어야 합니다!

print("정답 = (((()))) / 현재 풀이 값 = ", get_correct_parentheses(")()()()("))
print("정답 = ()()( / 현재 풀이 값 = ", get_correct_parentheses("))()("))
print("정답 = ((((()())))) / 현재 풀이 값 = ", get_correct_parentheses(')()()()(())('))

4. 느낀점⭐

1) 대기업 코테는 정말 어렵다.. 생각하는 힘 뿐만 아니라 코드의 흐름 자체가 어렵다.. 일단 알맞은 자료구조를 선택하고 활용하여 로직을 구현하는게 정말 어렵다.
2) 자바의 다형성을 위한 법칙들의 존재에 대한 이유를 몰랐는데 코드로 이해하니 알 것 같다.
3) 만약 프로젝트를 진행한다고 했을 때, 공통적인 특성을 묶고, 상속 or 구현을 활용하여 파고든다면 정말 어려울 것 같다.

profile
기록하는 습관

0개의 댓글