별찍기, 파일 입출력, IOException, Interface, DI, IOC

calis_ws·2023년 5월 10일
0

다이아몬드

  • 4주차 3일 수업로그
public class DrawDiamond {
    public static String getRepeatedSymbol(String symbol, int n) {
        return symbol.repeat(n);
    }
    public static void main(String[] args) {
        int h = 9;
        int pivot = h / 2;
        for (int i = 0; i < h; i++) {
            if (i <= pivot) {
                // 피라미드 로직
                // 초항이 3 등차가 -2
                System.out.printf("%s%s\n", getRepeatedSymbol(" ", pivot - i), getRepeatedSymbol("*", 2 * i + 1));
            } else {
                // 역피라미드 로직
                System.out.printf("%s%s\n", getRepeatedSymbol(" ", i - pivot), getRepeatedSymbol("*", 2 * (h - i) - 1));
            }
        }
    }
}

별찍기 Abstract Class를 적용한 추상화

public abstract class ShapeDrawer2 {	// 추상 클래스
    public abstract String makeALine(int h, int i);
    public void printShape(int height) {
        for (int i = 0; i < height; i++) {
            System.out.printf("%s", makeALine(height, i));
        }
    }				// 별찍기 출력 메소드
}

직각삼각형

public class RightTriangleShapeDrawer extends ShapeDrawer2 { 	// 직각 삼각형 상속(확장)

    @Override
    public String makeALine(int h, int i) {
        return String.format("%s%s\n", "", "*".repeat(i + 1));
    }

    public static void main(String[] args) {
        RightTriangleShapeDrawer rts = new RightTriangleShapeDrawer();
        rts.printShape(5);
    }
}

피라미드

public class PyramidShapeDrawer extends ShapeDrawer2 {		// 피라미드 상속(확장)

    @Override
    public String makeALine(int h, int i) {
        return String.format("%s%s\n", " ".repeat(h - (i + 1)), "*".repeat(2 * i + 1));
    }

    public static void main(String[] args) {
        ShapeDrawer2 rightTriangle = new PyramidShapeDrawer();
        rightTriangle.printShape(5);
    }
}

다이아몬드

public class DiamondShapeDrawer extends ShapeDrawer2 {		 // 다이아몬드 상속(확장)
    public static String getRepeatedSymbol(String symbol, int n) {
        return symbol.repeat(n);
    }
    @Override
    public String makeALine(int h, int i) {
        int pivot = h / 2;
            if (i <= pivot) {
                return String.format("%s%s\n", getRepeatedSymbol(" ", pivot - i), getRepeatedSymbol("*", 2 * i + 1));
            } else {
                return String.format("%s%s\n", getRepeatedSymbol(" ", i - pivot), getRepeatedSymbol("*", 2 * (h - i) - 1));
            }
        }
    public static void main (String[] args){
        DiamondShapeDrawer dsd = new DiamondShapeDrawer();
        dsd.printShape(5);
    }
}

강사님의 분리 과정

최초 코드

main에서 하지 않아야 할 구체적인 연산 작업, 출력, 반복이 모두 들어 있습니다.
public class RightTriangleDraw {
   public static void main(String[] args) {
       int h = 5;
       for (int i = 1; i <= h; i++) {
           System.out.printf("%s%s\n", "0".repeat(h - i), "*".repeat(2 * i - 1));
       }
   }
}

메소드로 분리

main 에서는 인스턴스 생성과 호출만 하도록 변경 합니다.
public class RightTriangleDraw {
   public void printShape() {
       int h = 5;
       for (int i = 1; i <= h; i++) {
           System.out.printf("%s%s\n", "0".repeat(h - i), "*".repeat(2 * i - 1));
       }
   }
  
   public static void main(String[] args) {
       RightTriangleDraw rtd = new RightTriangleDraw(); // Instance 생성
       rtd.printShape(); // 호출
   }
}

메소드로 추상화

makeALine()
public class RightTriangleDraw {
  
   public String makeALine(int h, int i) {
       return String.format("%s%s\n", "0".repeat(h - i), "*".repeat(2 * i - 1));
   }
  
   public void printShape(int height) {
       for (int i = 1; i <= height; i++) {
           System.out.printf("%s", makeALine(height, i));
       }
   }

   public static void main(String[] args) {
       RightTriangleDraw rtd = new RightTriangleDraw();
       rtd.printShape(5);
   }
}

Abstract Class적용

추상클래스 : 적어도 1개의 추상 메소드가 있는 Class
public abstract class ShapeDrawer2 {

   public abstract String makeALine(int h, int i);
//    {
//        return String.format("%s%s\n", "0".repeat(h - i), "*".repeat(2 * i - 1));
//    }

   public void printShape(int height) {
       for (int i = 1; i <= height; i++) {
           System.out.printf("%s", makeALine(height, i));
       }
   }
}

ShapeDrawer2 상속받기

public class <클래스명> extends <추상클래스명>
<추상클래스명> Alt + Enter
public class RightTriangleShapeDrawer extends ShapeDrawer2{
   @Override
   public String makeALine(int h, int i) {
       return null;
   }
}

완성

public class RightTriangleShapeDrawer extends ShapeDrawer2{
   @Override
   public String makeALine(int h, int i) {
       return String.format("%s%s\n", "", "*".repeat(i));
   }

   public static void main(String[] args) {
       ShapeDrawer2 rightTriangle = new RightTriangleShapeDrawer();
       rightTriangle.printShape(5);
   }
}

Interface(인터페이스)

인터페이스란?

  • 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다.
  • 인터페이스의 내용을 실제로 구현한 클래스를 '구현 클래스'라고 한다.

Abstract와의 차이점

  • abstract 클래스는 추상 메소드뿐만 아니라 생성자, 필드, 일반 메소드도 포함한다.
  • 반면 인터페이스는 오로지 abstract 메소드와 final 선언된 필드만을 포함할 수 있다.
  • abstract 클래스는 다중 상속을 지원하지 않는다.
  • 반면 인터페이스는 다중 상속을 지원한다.

인터페이스의 선언

인터페이스를 선언할 때에는 접근 제어자와 함께 interface 키워드를 사용하면 된다.

public interface InterfaceExample{
		//final 선언이 되어 있어야 한다! 
    public static final finalVariableEx = 10;

		//abstract 선언이 되어 있어야 한다.
    public abstract void methodEx(int paramEx);
}

//구현할 필요가 없으므로 중괄호 { } 대신 세미콜론 ; 으로 끝마친다.

Ex)

간단한 출력을 담당하는 print() 메소드만을 가지고 있는 인터페이스

import java.io.IOException;

public interface Printer {
    void print(String[] lines) throws IOException;
}
Printer 인터페이스를 implements하는 구현 클래스

import java.io.IOException;

public class ConsolePrinter implements Printer{
    @Override
    public void print(String[] lines) throws IOException {
        for(int i = 0; i < lines.length; i++) System.out.print(lines[i]);
    }
}

인터페이스의 장점

  • 일관되고 정형화된 개발을 위한 표준화가 가능하다.
  • 기능의 확장이 용이하며, 개발 시간이 단축된다.

Printer 인터페이스 실습

Printer 인터페이스

import java.io.IOException;

public interface Printer {
    void print(String[] lines) throws IOException;
}

Printer 인터페이스를 구현한 ConsolePrinter 클래스

import java.io.IOException;

public class ConsolePrinter implements Printer{
		
		//Console에 출력하는 메소드입니다.
    @Override
    public void print(String[] lines) throws IOException {
        for(int i = 0; i < lines.length; i++) System.out.print(lines[i]);
    }
}

Printer 인터페이스를 구현한 FilePrinter 클래스

public class FilePrinter implements Printer {
		//File로 출력하는 메소드입니다.
    @Override
    public void print(String[] lines) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("./src/main/java/org/example/week4/day3/RightTriangle.txt"));
        for(int i = 0; i < lines.length; i++) bw.append(lines[i]);

        bw.flush();
        bw.close();
    }
}

Printer 적용한 RightTrianglePrinter (문제점 존재)

import java.io.IOException;

public class RightTrianglePrinter {
		//Printer 인터페이스를 활용. 
		//그러나 ConsolePrinter라는 구체적인 구현 클래스가 같이 적혀있다.
    private Printer printer = new ConsolePrinter();

    public String makeALine(int h, int i){
        return String.format("%s%s\n", " ".repeat(h-i - 1), "*".repeat(i + 1));
    }
    //모양 출력하기
    public void printShape(int h) throws IOException {
        String[] lines = new String[h];
        for (int i = 0; i < h; i++){
            lines[i] =makeALine(h, i);
        }

        printer.print(lines);

    }

    public static void main(String[] args) throws IOException {
        RightTrianglePrinter rtp = new RightTrianglePrinter();
        rtp.printShape(5);
    }
}

Printer Interface를 통해 print( ) 메소드를 지원하는 어떤 클래스든지 사용할 수 있도록 기능을 확장했지만 인터페이스를 사용한 의미가 없이 ConsolePrinter라는 구현 클래스가 같이 적혀있다.

private ConsolePrinter printer = new ConsolePrinter();

이렇게 사용하는 것과 다를게 없기 때문에
굳이 인터페이스를 사용할 이유가 없어지는 것이다. (IOC 적용X)

이것을 극복하기 위해서는 생성자를 통해 어떤 구현체를 사용할지 결정하도록 하여 어떤 printer를 선택할지의 책임을 외부에 넘길 수가 있다.

생성자를 통해 DI하게 적용한 RightTrianglePrinter

import java.io.IOException;

public class RightTrianglePrinter {
		//중요한 부분 1
    private Printer printer;

		//중요한 부분 2 : 생성자를 통해 주입 (Dependency Injection - DI)
    public RightTrianglePrinter(Printer printer) {
        this.printer = printer;
    }

    public String makeALine(int h, int i){
        return String.format("%s%s\n", " ".repeat(h-i - 1), "*".repeat(i + 1));
    }
    //모양 출력하기
    public void printShape(int h) throws IOException {
        String[] lines = new String[h];
        for (int i = 0; i < h; i++){
            lines[i] =makeALine(h, i);
        }

        printer.print(lines);

    }
    public static void main(String[] args) throws IOException {
				//중요한 부분 3
        RightTrianglePrinter rtp = new RightTrianglePrinter(new ConsolePrinter());
        rtp.printShape(5);
    }
}

RightTrianglePrinter 내부에 ConsolePrinter가 적혀 있지 않다.
생성자를 통해 Printer 인터페이스에 속하는 구현 클래스를 받아서 사용하는 것이다.

어떤 구현체를 사용할지는 RightTrianglePrinter를 호출하는 외부에서 결정하는데
이렇게 외부에서 프로그래머가 작성한 코드를 호출하고, 흐름을 제어하는 것을 'IOC'라고 한다.

그리고 외부에서 코드의 흐름이 의존하는 구현 클래스를 주입 받는 것을
DI (Dependency Injection - 의존성 주입)이라고 한다.

  • Q : RightTrianglePrinter 내부에 ConsolePrinter가 적혀있지 않다고 하셨는데, main 메소드에 ConsolePrinter가 적혀있는데요?

  • A : main 메소드는 정확히 말하면 RightTrianglePrinter 내부라기 보다는 static 영역에 존재하는 것이라고 볼 수 있습니다.
    만약 헷갈린다면 RightTrianglePrinter 내부에 존재하는 main을 지우고, RightTrianglePrinterTest라는 클래스를 만든 후에 거기에서 똑같은 내용의 main 메소드를 적어서 실행시켜 보시면 됩니다.

파일 입출력

  • 파일 입출력 : Scanner, System.out.print() 방식이 아닌 파일을 통해 입출력을 한다.
  • 파일 입출력 방식
    1. FileWriter를 단독으로 사용 가능하지만 BufferedWriter와 같이 사용 시 속도가 빠름

    2. BufferedReader : 버퍼를 사용한 입력 클래스

    3. BufferedWriter : 버퍼를 사용한 출력 클래스

    4. FileReader : 전달한 경로의 파일을 읽어들이기 위한 클래스이며, 텍스트 파일을 자바로 읽어올 때 사용

      • 전달한 경로에 파일이 없다면 FileNotFoundException 발생
    5. FileWriter : 전달한 경로의 파일을 출력하기 위한 클래스

      • 전달한 경로에 파일이 없다면 새롭게 만든 후 실행

      • 파일이 이미 존재한다면 덮어쓰게 됨

입력
BufferedReader br = new BufferedReader(new FileReader(파일이름 및 경로));

출력
BufferedWriter bw = new BufferedWriter(new FileWirter(파일이름 및 경로));

throws IOException(예외 처리)

  • ‘throws’ 는 메소드나 생성자에서 발생할 수 있는 예외를 호출한 메소드로 던지는 것을 의미합니다.

  • ‘IOException’ 은 Java에서 발생할 수 있는 예외 클래스 중 하나입니다. 입출력 작업 중 발생하는 예외를 처리하기 위해 사용됩니다.

  • Input, Output(입출력)을 하는 과정에서 발생하는 예외

    1. 파일이 존재하지 않는경우

    2. 파일에 읽기 또는 쓰기 권한이 없는 경우

    3. 네트워크를 이용한 통신 작업을 할 경우

    입출력 작업을 수행하는 코드에서는 IOException 예외를 처리하는 것이 중요하다. 호출하는 쪽에서 예외 처리를 하지 않는다면 컴파일 오류가 발생할 수 있기 때문이다.

IOC(Inversion of control, 제어의 역전)

  • 프로그램 내부에 존재하는 객체들과 그 객체들의 의존성을 프레임워크나 컨테이너와 같은 외부에서 제어하는 것

  • Spring에서 자주 사용 → Spring 전엔 개발자가 프로그램의 흐름(애플리케이션 코드) 제어 주체

  • 제어권이 컨테이너로 넘어감(Spring에서 추후 배울 것)

  • IOC를 통해 DI(의존성 주입) 사용 가능

OOP의 원칙 - SOLID

  1. SRP(Single Responsibility Principle): 단일 책임 원칙

  2. OCP(Open Closed Principle): 개방 폐쇄 원칙

  3. LSP(Liskov Substitution Principle): 리스코프 치환 원칙

  4. ISP(Interface Segregation Principle): 인터페이스 분리 원칙

  5. DIP(Dependency Inversion Principle): 의존 역전 원칙

Strategy Pattern (전략 패턴)

  • Spring은 이것으로 구성

  • ConsolePrinter를 쓸 지 or FilePrinter를 쓸지 선택하는 디자인 패턴

  • IoC(제어의 역전)와 DI(의존성 주입)를 사용하는 패턴
    (현업에서 자주 사용하므로 꼭 이해 및 적용 필수)

    출처 : 멋사 5기 백엔드 위키 6팀 식스센스

인사이트 타임

백엔드 위키 작성 올타임

review

오늘도 어제 못지 않게 진도도 팍팍 나가고 난이도가 장난 아니었다.
메소드 파트 진입하고 부터 상당한 고통을 받고 있다. 수업시간에 전부 따라치기만 가능하고 이해하기엔 버거운 상태에 있다. 많은 수강생들도 같은 상황에 놓여 있어서 그런지 강사님이 속도를 조금 늦춰주셨다.

위키 작성하는데도 이해가 완전히 되지 않은 상태에서 설명을 적는 것에 어려움을 느꼈다. 아마 오늘 강사님의 진도 목적이었던 미니 프로젝트까지 했다면 위키 작성하느라 빼박 야근 했을지도 모른다.
오늘 너무 어렵고 힘든날이었음에도 불구하고 전공자이신 태환님과 원기님의 7할분량으로 고속버스를 태워주셔서 정말 다행이고 감사하다. 한편으로는 매번 도움만 받느라 죄송하다.
팀원들이 각자 멋있고 적극적이고 소심하고 격려하고 배려하는 모습들이 다양하게 있는 것 같다. 역시 우리팀이 최고라고 생각한다.

강사님 피셜로 난이도가 높은 파트는 맞지만 이 수준에 머물러야 성장이 가능하다고 하셨다. 내가 버틸수 있을까 나도 궁금하다. 일단 내일만 살자.

profile
반갑습니다람지

0개의 댓글