FE

Frontend #


2025-09-02 ⋯ Java #2 객체지향 설계 원칙 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) 6. 공통 특성: 응집도를 높이거나 결합도를 낮추는 설계 1. 단일 책임 원칙 (Single Responsibility Principle, SRP) 정의 - 한 클래스는 하나의 책임만 가져야 하고 클래스가 변경되어야 할 이유는 오직 하나여야 한다. SRP 위반 예제 의문점 Employee 클래스가 3가지 역할을 동시에 하게되는게 문제가되는이유? - 기능이 섞여 있으면 한 영역을 고치면서 다른 영역에 코드 충돌 위험이 커진다. - 콘솔 대신 파일 출력으로 바꾸려고 printEmployeeInfo()를 수정했는데, 그 과정에서 calculatePay() 관련 필드를 잘못 건드려 급여 계산이 틀려버릴 수 있다 - 작은 변경에도 클래스 전체를 건드려야해서 유지보수성이 떨어진다. - 출력 로직 바꾸려고 Employee 클래스를 열면, 급여 계산과 데이터 관리 코드까지 다 보여서 코드 접근에 대한 불확실성이 커지고 불필요하게 큰리스크를 안게된다. - 결론 - 3가지 이유로 고친다고 해서 실행이 안되는 건 아니지만 실무에서는 한 클래스에는 하나의 책임만 부여하는 것이 장기적으로 안전하고 효율적이다. 2. 개방-폐쇄 원칙 (Open-Closed Principle, OCP) 정의 - 소프트웨어 요소(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다. OCP 위반 예제 의문점 class AnimalSound에서 수정이 항상 따라온다는것의 의미? - - 새로운 동물이 추가될 때마다 기존에 이미 잘 동작하고 있던 AnimalSound 클래스의 코드를 바꿔야 한다. - 그러면 OCP의 핵심인 '새로운 기능은 추가할 수 있지만, 기존 기능은 그대로 두어야 한다'가 위배된다. - 새로운 동물을 추가해도 AnimalSound라는 기존 클래스의 내부 코드를 건드리지 않아도 되는 다음과 같은 형태여야한다. -> 이렇게 만들면 Horse 같은 새로운 동물이 추가되더라도 AnimalSound는 전혀 수정하지 않고 그대로 재사용할수있음. 3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP) 정의 - 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다. - 상속 관계에서 부모 타입으로 선언된 객체 자리에 자식 객체를 넣어도 프로그램이 정상적으로 동작해야 한다. LSP 위반 예제 의문점 Bird bird2 = new Penguin();에서 펭귄 객체에 ‘인스턴스와 객체의 분리’가 어떻게 적용되는가? + 컴파일 시점 타입과 실행 시점 타입의 차이가 어떻게 LSP 위반으로 이어지는가? - 객체는 클래스라는 설계도로부터 생성된 실체. 즉 new Penguin()으로 생성된 펭귄. - 인스턴스는 어떤 클래스의 “구체적인 사례”라는 의미에서 바라본 객체. 즉 Penguin penguin = new Penguin();이면 penguin은? - "Penguin 클래스의 인스턴스"이자 동시에 "Bird 클래스의 인스턴스". - Penguin의 사례이자 Bird의 사례. - 모든 인스턴스는 객체이지만 객체를 어떤 타입 관점에서 바라보느냐에 따라 인스턴스라고 부른다. - Bird bird2 = new Penguin();에서 - 실제로 만들어진 것은 Penguin 객체이고 - 이 객체는 Penguin 클래스의 인스턴스임과 동시에 Penguin이 Bird를 상속했기 때문에 Bird 클래스의 인스턴스. 따라서 bird2라는 참조 변수는 Bird 타입을 기준으로 이 객체를 다룬다. 여기서 “타입은 Bird, 실제 객체는 Penguin” 이라는 분리가 발생한다. - 컴파일 vs 실행 - bird2 변수의 정적 타입(compile-time type) 은 Bird이므로 bird2.fly() 호출은 컴파일러가 허용한다. 하지만 실제 실행 시점(run-time type) 은 Penguin이므로 Penguin.fly() 가 실행되며 UnsupportedOperationException 처리된다 즉, Bird라는 부모 타입의 계약(fly() 가능하다) 은 Penguin 객체에서는 깨져버린다. - 결론 - 인스턴스와 객체의 분리는 "Bird 타입 인스턴스로서의 펭귄"이라는 다형성 상황을 만들어주지만 펭귄이 fly() 계약을 제대로 지키지 못하면서 LSP 위반이 발생했다. 4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) 정의 - 하나의 범용적인 큰 인터페이스보다는 여러 개의 구체적이고 작은 인터페이스로 나누는 것이 좋다. ISP 위반 예제 5. 의존 역전 원칙 (Dependency Inversion Principle, DIP) 정의 - 고수준 모듈은 저수준 모듈에 의존하면 안 된다. - 상위 비즈니스 로직이 하위 세부 구현에 직접 묶이지 않고, 추상화(인터페이스)에 의존해야 한다. DIP 위반 예제 의문점 이 코드가 DIP를 위반하는 이유? (고수준 모듈 vs 저수준 모듈) - 의존 역전 원칙: 고수준 모듈은 저수준 모듈에 의존하지 말고, 둘 다 추상화에 의존해야 한다. - 고수준 모듈인 Car는 - “주행” = drive()이라는 목표만 있으면 되는데 new SnowTire()를 해 버리면서, 특정 부품인 스노우 타이어와 묶이게 됨 - 결론 - 이 코드가 DIP를 위반하는 이유는 고수준 모듈이 목적(drive())보다 수단(tire)에 자신을 종속시켜서. - Car은 ‘Tire 인터페이스’에만 의존하고 실제 어떤 타이어를 쓸지는 외부에서 주입(Dependency Injection)받아야 한다. “Car(고수준모듈)이 Tire 인터페이스에만 의존하고 실제 어떤 타이어를 쓸지는 외부에서 주입받아야 한다”의 의미? - Tire 인터페이스 - 타이어라면 반드시 roll() 기능을 제공해야 한다. - 타이어 - Tire 인터페이스를 지키면서 자기 방식대로 동작하는 타이어 (저수준 모듈/아래 코드에서 SnowTire, NormalTire) - Car(고수준모듈)이 Tire 인터페이스에만 의존해야한다: - 타이어 2종류: SnowTire, NormalTire - class Car에서 타이어 관련 코드를 보면 public Car(Tire tire) { // 외부에서 주입 this.tire = tire; } 니까 특정 타이어 종류랑 묶여있지 않음 - 실제 어떤 타이어를 쓸지를 외부에서 주입: - new Car(new SnowTire()) 에서 실제 어떤 타이어가 들어올지는 실행 시점에 외부에서 결정된다. - 그래서 Car는 본질적인 책임(주행)에만 집중할 수 있고 타이어의 종류가 바뀌어도 Car 클래스 자체는 수정할 필요가 없다. - 결론 - Car는 추상화(Tire 인터페이스)에만 의존하고, 구체적인 객체 생성과 선택은 외부(Main)에서 맡게 됨으로써 결합도를 낮추고 유연성을 확보한다. 6. 공통 특성: 응집도를 높이거나 결합도를 낮추는 설계 SOLID 객체지향 설계 원칙은 모듈 간 결합도는 낮추고 각 모듈 내부의 응집도는 높여서 일관성있고 유연한 구조를 만드는게 목적. - SRP (단일 책임 원칙): 클래스가 한 가지 책임만 가지게 해서 응집도를 높임. - OCP (개방-폐쇄 원칙): 확장에는 열려 있고 변경에는 닫혀 있게 해서 코드 변경 없이 새로운 기능을 붙일 수 있게 해서 응집도를 유지하면서 변화에 유연하게 설계. - LSP (리스코프 치환 원칙): 부모 타입을 대체할 수 있는 자식 타입을 보장해서 결합도를 낮추면서 일관성 있게 설계. - ISP (인터페이스 분리 원칙): 불필요한 의존성을 줄이고 필요한 인터페이스만 사용하게 해서 결합도를 낮추고 응집도를 높임. - DIP (의존 역전 원칙): 고수준 모듈과 저수준 모듈이 추상화에 의존하도록 해서 결합도를 낮추고 응집도를 강화.


2025-09-01 ⋯ Java #1 객체지향 프로그래밍: 캡슐화, 추상화, 다형성, 상속

목차 1. 캡슐화 2. 추상화 3. 다형성 4. 상속 5. 공통 특성: 인터페이스와 구현의 분리 1. 캡슐화 개념 및 목적 - 개념 - 객체지향 프로그래밍에서 객체의 속성(필드)을 외부로부터 숨기고, 공개된 메서드(getter/setter)를 통해서만 접근하도록 만드는 원칙 - 필드를 private으로 선언하고, 외부에서 직접 접근하지 못하게 제한하고, public 메서드인 getter와 setter를 제공해 값을 읽거나 수정할 수 있도록 한다. setter 내부에는 유효성 검사 로직을 넣어 잘못된 값이 들어오는 것을 막을 수도 있다. - 목적 1. 데이터 보호: 잘못된 값이 직접 들어가는 것을 막고, setter 내부에서 규칙을 강제함으로써 객체의 상태를 안정적으로 유지 2. 정보 은닉: 내부 구현이 어떻게 되어 있는지는 숨겨 두고, 외부에는 단순한 사용 방법만 제공함으로써 객체 사용자가 불필요한 복잡성을 신경 쓰지 않도록 한다. 3. 유지보수와 확장성: 내부 로직이 바뀌더라도 외부 인터페이스(getter/setter)가 같으면 사용하는 코드는 수정할 필요가 없으므로 프로그램 전체의 안정성이 높아지고 유지보수가 쉬워진다. 샘플 코드 class Stock - private String name; - 주식의 이름(예: "스칼라 AI") - private이기 때문에 클래스 외부에서는 s1.name처럼 직접 접근 불가 - private double price; - 주식의 가격을 저장하는 변수 - private이기 때문에 클래스 외부에서는 직접 접근 불가 - public Stock - this.name = name; - 생성 시 입력된 이름을 객체의 name 에 저장 - setPrice(price); - 가격은 바로 대입하지 않고 setPrice() 메서드를 통해 저장 - Getter - getName(): 주식 이름 - getPrice(): 주식 가격 - Setter - if (price > 0): 유효성 검사 - 올바른 가격(0보다 큰 수)이면 저장, 잘못된 값이면 거부하고 메시지를 출력하기. public class EncapsulationExample - public static void main(String[] args) - Stock 객체를 실제로 만들어서 테스트하는 클래스. - Stock s1 = new Stock("스칼라 AI", 17000); - "스칼라 AI"라는 이름과 17000이라는 가격으로 객체 생성. - 생성자 내부에서 setPrice(17000)이 호출되므로 유효성 검사가 통과되므로 저장된다. - System.out.println(s1.getName() + " 현재가: " + s1.getPrice()); - getName()과 getPrice()로 값을 출력 - s1.setPrice(18000); - setter를 통한 가격 변경 - setPrice(18000)은 유효성 검사를 통과하므로 price가 18000으로 업데이트된다. - setPrice(-5000) - setter 내부 조건문이 거부예정. - "잘못된 가격: -5000" 메시지만 출력되고, price 값은 바뀌지 않고, getPrice()로 확인하면 여전히 이전 값 18000이 유지된다. 의문점 캡슐화의 의미? - 중요한 데이터는 직접 노출하지 않고 private으로 은닉하며, getter/setter 같은 메서드를 통해서만 접근하도록 만들기. `this.price = price;` 하지않고 `setPrice(price)` 한 이유? - 이 값이 올바른지 아닌지 검사하는 로직을 넣기위해서. - 생성자에서 this.price = price;를 바로 쓰면 잘못된 값도 그대로 들어와 버릴 수 있다. 예를 들어 new Stock("삼성", -1000) 같은 유효하지않은 객체가 생성될수있는데 setPrice(price);를 쓰면 생성되는 순간에 그 값이 유효한지 검사하고 잘못된 값은 차단할 수 있다. - 결론 - 생성자 안에서 직접 대입하지 않고 setter를 호출하면 내부 로직이 항상 같은 규칙을 따르게 함으로써 어디서 값을 넣든지 간에 일관성과 안전성이 유지된다. 2. 추상화 개념 및 목적 - 개념 - 객체지향 프로그래밍에서 복잡한 시스템을 단순화하기 위해 핵심적인 개념과 동작만 남기고 불필요한 세부사항을 감추는 원칙 - 추상 클래스와 인터페이스 - 추상 클래스: 공통된 속성과 기본 동작을 정의하면서, 일부 메서드를 추상 메서드로 남겨 자식 클래스가 반드시 구현하도록 한다. - 인터페이스: 특정 기능에 대한 규약을 정의하며, 이를 구현하는 클래스가 해당 메서드를 구체적으로 작성하도록 강제한다. - 목적 1. 복잡성 단순화: 사용자나 개발자는 내부의 복잡한 구조를 알 필요 없이, 제공되는 메서드 시그니처만 보고 객체를 사용할 수 있다. 2. 코드의 유연성과 유지보수성 향상: 외부에서 바라보는 표면(메서드 선언)만 일정하게 유지하면 내부 구현은 자유롭게 변경하거나 최적화할 수 있다. 3. 일관성과 확장성 확보: 추상 클래스는 공통 뼈대를 재사용하게 해주고, 인터페이스는 다양한 클래스들이 동일한 규약을 따르도록 만들어 여러 객체를 일관된 방식으로 다룰 수 있게 한다. 이를 통해 협업과 테스트가 쉬워지고, 새로운 기능 확장이 용이해진다. 샘플 코드 추상 클래스 Asset - abstract class Asset - 추상 클래스 정의 - protected String name, protected double price - name, price 필드 - protected 접근제어자 사용해서 같은 패키지 내부와 자식 클래스에서만 접근 가능하게한다. - public Asset(String name, double price) {this.name = name; this.price = price;} - name과 price를 초기화 - public abstract void printInfo(); - printInfo()는 추상 메서드로 선언되어 있고 구현은 없다. - sset을 상속받는 자식 클래스들은 반드시 printInfo()를 구현해야한다 즉 Asset은 "공통 자산"이라는 추상적인 개념만 정의하고 구체적인 세부 내용은 자식 클래스에서 맡기는 구조. 인터페이스 Valuable - interface Valuable - 객체가 가져야 할 행동 규약 - void printInfo(); - 선언만 되어 있고 구현은 없음. 인터페이스를 구현하는 클래스는 반드시 이 메서드를 작성해야한다. - default void updatePrice(double price) {System.out.println("가격을 " + price + "원으로 업데이트했습니다.");} - 기본 구현: “가격을 업데이트했습니다”라는 메시지를 출력하기. Stock 클래스 - class Stock extends Asset implements Valuable - 추상 클래스 Asset을 상속하고 인터페이스 Valuable을 구현한다. - public Stock(String name, double price) {super(name, price);} - 생성자가 부모 클래스 Asset의 생성자를 호출해 name, price를 초기화한다. - @Override public void printInfo() - printInfo() 메서드를 오버라이딩하여 일반주 종목 정보를 출력. PreferredStock 클래스 - PreferredStock extends Asset implements Valuable - `Asset`을 상속, `Valuable`을 구현. - private double dividendRate; - 추가로 `dividendRate`(배당률)라는 필드를 가짐. - public PreferredStock(String name, double price, double dividendRate) {super(name, price); this.dividendRate = dividendRate; } - 생성자를 통해 `name`, `price`, `dividendRate`를 초기화. - @Override public void printInfo() - `printInfo()`를 오버라이딩하여 우선주의 정보(배당률 포함)를 출력하기. 의문점 Asset, Valuable의 Stock으로의 흐름과 Asset, Valuable의 PreferredStock으로의 흐름? - 추상 클래스 Asset - “모든 자산이라면 name과 price를 가져야 하며, 자신을 소개하는 방법인 printInfo() 메서드를 반드시 가져야 한다”라는 기본 골격을 생성하고 printInfo()를 선언만 해둔다. - 인터페이스 Valuable - “가치 있는 자산이라면 반드시 printInfo()를 구현해야 한다”라는 규약을 정의하고, 추가로 updatePrice(double price)라는 기본 기능을 메뉴얼에 적어둔다. - Stock 클래스 1. Asset을 상속받아서 name과 price 필드를 물려받음 2. printInfo()를 구현하면서 “나는 일반주이고, 종목명은 name, 현재가는 price원이다”라는 구체적인 출력 내용을 정의 3. 동시에 Valuable 인터페이스를 구현 4. 규약을 확인해보니 printInfo()는 이미 Asset에서 추상 메서드로 선언되어 있었고, Stock이 그것을 구체적으로 작성했으므로 인터페이스 규약을 만족 5. Valuable 인터페이스를 구현했으므로 printInfo()와 updatePrice(double price) 메서드를 사용할 수 있음 6. 결국 Asset에서 내려온 골격(name, price, printInfo())과 Valuable에서 정한 규칙 및 기능(printInfo(), updatePrice(double price))이 Stock 클래스 안에서 결합됨 - PreferredStock 클래스 1. Asset을 상속받아 기본 필드인 name과 price를 물려받고, printInfo를 구현 2. 일반주와는 다르게 배당률이라는 고유한 특징이 있으므로 새로운 필드 dividendRate를 추가. 3. printInfo에서는 이름, 가격과 함께 배당률도 출력. - 결론 - Asset이 제공하는 공통 골격(name, price, printInfo())과 Valuable이 정한 규약(printInfo()) 및 기능(updatePrice(double price))이 Stock과 PreferredStock에 각각 결합되어 Stock은 일반주로서 name과 price를 출력하고 PreferredStock은 여기에 dividendRate를 더해 고유 특성을 반영한다. 3. 다형성 개념 및 목적 - 개념 - 객체지향 프로그래밍에서 하나의 타입으로 여러 형태의 동작을 표현 즉 같은 이름의 메서드를 호출하더라도 객체의 실제 타입에 따라 실행되는 동작이 달라지는 특성 - 이를 가능하게 하는 조건은 상속과 메서드 오버라이딩으로 구현되고 보통 업캐스팅과 함께 활용된다. - 부모 클래스 타입의 참조 변수를 통해 메서드를 호출하면, 실행 시점에는 실제 객체 타입에 맞는 오버라이딩된 메서드가 실행된다. - 목적 - 코드의 유연성 확보: 하나의 부모 타입으로 여러 자식 객체를 다룰 수 있기 때문에, 코드 구조를 단순하게 유지하면서 다양한 객체를 일관된 방식으로 처리할 수 있어서 새로운 자식 클래스가 추가되더라도 기존 코드를 거의 수정하지 않고 확장이 가능하다. 샘플 코드 Stock - class Stock - 주식 개념 부모 클래스 - protected String name; protected double price; - 주식의 이름과 가격을 저장하는 필드(멤버 변수) - protected - 같은 패키지 내부나 상속받은 자식 클래스에서 접근 가능하다. 외부에서는 직접 접근 불가하다. - `public Stock(String name, double price)` - 생성자(Constructor) - `name`과 `price`를 받아 초기화 - `public void printInfo()` - 주식 정보를 출력 - System.out.println("[일반주] 종목: " + name + ", 가격: " + price + "원"); - “일반주”라고 표시하고 종목명과 가격을 보여준다 - 자식 클래스에서 오버라이딩 대상인 메서드 PreferredStock - class PreferredStock extends Stock - Stock을 상속받은 자식 클래스. - 상속을 통해 name과 price를 물려받았는데 배당률(dividendRate)이라는 속성을 추가하여 “우선주”를 구체화함. - super(name, price); - 부모 클래스의 생성자를 호출 - @Override public void printInfo() - 부모 클래스 printInfo()를 오버라이딩 - 실행 시점에는 동적 바인딩에 의해, 객체의 실제 타입이 PreferredStock이면 이 메서드가 실행된다. - public void showDividend() - 자식 클래스에만 있는 메서드. 배당률을 따로 출력하는 기능. - 부모 타입 변수로는 접근할 수 없고, 자식 타입으로 다운캐스팅해야 호출할 수 있다. 의문점 “@Override public void printInfo()를 오버라이딩하면 실행 시점에 객체의 실제 타입에 맞는 메서드가 호출된다”의 의미? - @Override public void printInfo()를 오버라이딩? - 부모 Stock에는 printInfo()가 있는데 자식 PreferredStock이 똑같은 메서드 시그니처(메서드 이름, 매개변수 목록, 반환형이 동일)로 다시 정의하면 그게 오버라이딩. - printInfo() 실행 시점에 객체의 실제 타입에 맞는 메서드가 호출된다? - printInfo()같은 인스턴스 메서드는 2단계로 처리되는데 1. 메서드 호출 - 컴파일러는? 변수의 선언 타입을 보고 “이 메서드를 불러도 되는지” 확인한다. - Stock s = new PreferredStock(...) 일때 s.printInfo(); 하면 s가 Stock 타입이니까, Stock 클래스에 printInfo()가 있는지만 확인한다. 2. 실제 구현 - JVM은? 실제 객체가 누구인지 확인하는데 - 지금 s가 참조하는 건 Stock이 아니라 PreferredStock 객체니까 “PreferredStock에 printInfo()가 오버라이딩돼 있네? 그럼 이걸 실행해야겠다.” 하고 결정한다. - 결론 - printInfo() 호출하면 컴파일러는 변수선언을 보고 s가 Stock 타입이고 Stock 안에 printInfo() 있으니까 호출 승인하고, 어떤 버전의 printInfo()가 실행될지는 아직 정해지지 않았고, JVM이 객체를 확인했을때 Stock객체라면 부모 클래스 버전 `printInfo`()이 실행, PreferredStock이라면 그 클래스에서 정의된`printInfo`()를 실행한다. 동적 바인딩? - 실행할 때 객체의 실제 타입을 보고 그에 맞는 메서드를 선택하는게 동적 바인딩. (s라는 변수가 Stock 타입으로 선언되어 있어도, new PreferredStock(...)로 만든 객체를 가리키고 있다면 자식 쪽에 오버라이딩된 메서드가 실행됨) 4. 상속 1. 개념 및 목적 - 개념 - 기존(부모) 클래스가 가진 속성과 메서드를 새로운(자식) 클래스가 계승하여 활용할 수 있도록 하는 개념 - 자식 클래스는 부모 클래스가 정의한 필드와 메서드를 직접 사용할 수 있다. - 필요에 따라 새로운 속성과 기능을 추가하거나, 부모 메서드를 오버라이딩(Overriding)하여 구체적인 동작을 재정의할 수 있고 - 이를 통해 자식 클래스는 부모 클래스가 제공하는 공통 기능을 기반으로 기본 구조와 일관성을 유지하면서도, 고유한 특성과 요구 사항을 반영하여 더욱 구체적이고 특화된 클래스로 확장될 수 있다. - 목적 1. 코드 재사용성: 부모 클래스에 정의된 공통 속성과 기능을 여러 자식 클래스에서 공유할 수 있어, 중복 코드를 줄이고 전체 코드 구조를 간결하게 만든다. 2. 유지보수성과 확장성: 공통 로직은 부모 클래스에만 수정하면 되고, 자식 클래스는 필요에 따라 기능을 덧붙이거나 오버라이딩을 통해 동작을 변경할 수 있어 유지보수가 쉽고 새로운 기능 추가도 용이하다. 3. 다형성 기반 마련: 부모 타입으로 자식 객체를 다룰 수 있고, 실행 시점에는 실제 객체의 타입에 맞는 동작이 수행되므로 유연한 구조를 만들 수 있다. 2. 샘플 코드 부모 클래스 Stock - class Stock - 주식 개념 부모 클래스 - protected String name; protected double price; - 주식의 이름과 가격을 저장하는 필드(멤버 변수) - protected - 같은 패키지 내부나 상속받은 자식 클래스에서 접근 가능하다. 외부에서는 직접 접근 불가하다. - `public Stock(String name, double price)` - 생성자(Constructor) - `name`과 `price`를 받아 초기화 - `public void printInfo()` - 주식 정보를 출력 - System.out.println("[일반주] 종목: " + name + ", 가격: " + price + "원"); - “일반주”라고 표시하고 종목명과 가격을 보여준다 - 자식 클래스에서 오버라이딩 대상인 메서드 자식 클래스 PreferredStock - class PreferredStock extends Stock - Stock을 상속받은 자식 클래스. - 상속을 통해 name과 price를 물려받았는데 배당률(dividendRate)이라는 속성을 추가하여 “우선주”를 구체화함. - super(name, price); - 부모 클래스의 생성자를 호출 - @Override public void printInfo() - 부모 클래스 printInfo()를 오버라이딩 - 실행 시점에는 동적 바인딩에 의해, 객체의 실제 타입이 PreferredStock이면 이 메서드가 실행된다. - public void showDividend() - 자식 클래스에만 있는 메서드. 배당률을 따로 출력하는 기능. - 부모 타입 변수로는 접근할 수 없고, 자식 타입으로 다운캐스팅해야 호출할 수 있다. 의문점 그래서 업캐스팅과 다운캐스팅이 어떻게적용되는가? - 업캐스팅 - 자식 객체를 부모 타입 변수에 담는것 - PreferredStock이 Stock을 상속받는 상황에서 Stock stock = new PreferredStock(...);처럼 쓰면 실제 객체는 PreferredStock이지만 참조 변수의 타입을 Stock으로 지정했기 때문에 컴파일러는 이 객체를 부모 클래스 객체 형식으로 인지한다. - 실행 시점에 stock.printInfo()를 호출하면 실제 객체가 PreferredStock이므로 자식이 오버라이딩한 메서드가 실행된다. 핵심은 부모객체처럼 인지되면서도 실제동작은 자식클래스의 성질이 반영된다. - 업캐스팅 하는이유? - 여러 종류의 자식 클래스를 일괄적으로 묶어서 처리할수있기때문에 코드가 단순해진다. - 다운캐스팅 - 부모 타입 변수에 들어 있는 객체를 다시 자식 타입 변수로 변환하는것. - Stock stock이라는 부모 타입 참조가 있지만, 실제 객체가 PreferredStock이라면 (PreferredStock) stock으로 형변환을 거치면 자식 타입 변수로 다룰수있다 즉 자식만이 가진 고유한 메서드 showDividend() 를 호출할수있다. - 결론 - 상속 구조에서는 같은 객체를 필요에 따라 업캐스팅 ↔ 다운캐스팅으로 부모 클래스 ↔ 자식 클래스로 바꿔 다루면서 공통성과 특수성을 효율적으로 반영할수있다. 5. 공통 특성: 인터페이스와 구현의 분리 - 캡슐화, 추상화, 다형성, 상속은 결국 인터페이스와 구현이 분리되는걸 활용하는게 포인트인것같은데 인터페이스와 구현의 분리가 각각 어떻게 활용되었는가? - 캡슐화 - 데이터(구현)를 숨기고 메서드(인터페이스)만 공개 - 추상화 - “무엇을 할 수 있는가”(인터페이스)와 “어떻게 할 것인가”(구현)를 분리 - 다형성 - 부모의 틀(인터페이스)은 유지하면서, 자식에서 구체 구현을 다양하게 정의 - 상속 - 호출하는 쪽은 부모 타입(인터페이스)만 보고, 실행되는 쪽은 실제 객체의 구현을 따른다.


2025-07-31 ⋯ HTML #2 SKCT 공부용 메모장+계산기 만들기

1. 문제 SKCT는 응시화면이 아래와같이 돼잇는데 [](https://community.linkareer.com/written_test/2629945) 연습하기 불편한거같애서 html로 만들어봣다 2. SKCT 공부용 메모장+계산기 https://github.com/yshghid/skct-tools/tree/main 요렇게 문제옆에 띄워놓고 쓰면됨 ㅎㅎㅎ 3. 수정사항 1. 메모장 ↔ 그림판 전환 버튼 - 메모장일때는 '🎨 그림판', 그림판일때는 '📝 메모장'이 뜨게 수정 1. 선 굵기 조절하는 슬라이더 넣기 - html: 슬라이더 UI 추가 - javascript: 초기 선 굵기 1로 설정 / 그림판 상태일때만 보기로 설정 2. 선 픽셀이 뭔가 깨져보임 - 디바이스 해상도(DPR: devicePixelRatio) 반영하여 캔버스 확장 - 원래 코드: 디스플레이 해상도를 고려하지 않고 canvas.width / canvas.height 를 설정 - 수정된 코드: dpr을 고려해서 width, height를 수정 3. 선색상, 지우개, 실행취소 기능 넣기 - "🧽 지우개" "✏️ 펜" 이렇게 2개 버튼 넣지말고 버튼 하나만 남겨서 펜 상태이면 "🧽 지우개", 지우개 상태면 "✏️ 펜"으로 변경 4. 지우개/펜 기본 두께 설정 / 지우개 색깔 수정 - 지우개 상태일때는 기본값이 9.5 / 펜 상태일때는 기본값이 3.0으로 - 지우개랑 배경색 통일시키기 - '🧽 지우개' 버튼을 클릭했을때 선 굵기가 9.5로 바뀌고나서 다시 "✏️ 펜" 버튼을 누르면 슬라이더가 원래 두께인 3.0로 돌아오지않고 9.5로 남는데 3.0로 따라오게. 1. *대신 × 쓰고싶은데 ×가 들어가니까 연산 오류가 남 - 입력창에는 ×를 보여주고 - 내부 계산 시에는 ×를 *로 변환하여 처리하는데 - display.value에는 ×가 포함되게 append()수정 원래 코드 / 변형코드 수정된 변형코드 이때 버튼은 × 유지 2차 수정 1. 그림판에서 기본 선 굵기가 3px라고 표시만 돼잇고 실제로는 1px 처럼보임 - canvas.getBoundingClientRect() 기반으로 크기를 조정한 후 ctx.setTransform(...)을 호출하면 브라우저의 devicePixelRatio에 따라 얇게 보일 수 있다. - resizeCanvas() 함수의 마지막에 ctx.lineWidth 설정을 추가, ctx.lineWidth = 3; 를 초기 설정 블록에서 한 번 더 강제 설정 2. 계산 결과 후 숫자를 누르면 자동 초기화 시키기 - 계산기에서 3+5하고 =하면 8 > 9를 누르면 8옆에 그냥 9가 적혀서 89가 됨 - 만약에 = 해서 연산 결과가 나온 상태면, 뒤에 숫자를 누르면 초기화(AC) 버튼을 안눌렀더라도 초기화(AC) 버튼을누르고 숫자를 누른걸로 인식하게 수정: justCalculated 플래그 도입 3. 계산기에서 키보드 입력(숫자 및 연산자 키)도 인식 - 계산기를 클릭뿐이아니라 키보드입력도 받게. - 유의사항 - 메모창이랑 섞이면 안됨. - 메모창을 누르면 키보드 입력이 메모창 내용만 수정해야하고 - 계산기 입력창을 누르면 키보드 입력이 계산기 입력 내용만 수정하게 분리. - 구현 - display 입력창에 focus되었을 때만 키보드 입력을 계산기에 전달 - 메모장(textarea)이 focus되면 계산기로 입력이 가지 않도록 제어 - keydown 이벤트를 window에 추가하고, 계산기 입력창이 focus일 때만 append() 호출 +) readonly 상태에서도 focus되도록 하려면 tabindex="0"을 넣어줘야 한다. 4. 계산기 입력창을 눌렀을 때 display.focus()를 명시적으로 호출해서 누르면 파란색으로 표시되게하기 5. 계산기에서 백스페이스, enter 누르면 = 처럼 쓰기, esc 누르면 AC 누른거랑 동일하게 쓰기 3차 수정 1. "."을 2번 이상 입력하려고 하면 무시(1번만 입력되게) 2. 0~9와 +-*/의 경우 키보드로 입력 가능하게 되어있는데 "."도 키보드로 입력 가능하게 3. 그림판에서 실행취소, 지우개만 있는데 초기화 버튼도 추가 4. .입력 후 /+-*같은 연산자가 입력 안되게 5. ~진짜 마지막..~ - 계산기에서 숫자나 연산기호를 누른상태에서 키보드 입력이 들어가면 focus가 숫자나 연산기호버튼으로 들어가있어서 입력창으로 키보드입력이 안들어감 - 계산기에서의 키보드입력은 계산기입력창으로만 들어가면 되니까 다음과같이 수정 - 키보드 입력은 무조건 계산기 입력창( 들어가도록 한다. - 단, 메모장( 포커스가 있을 때는 예외로 한다. - `const isInMemo = document.activeElement === textArea; if (isInMemo) return;`으로 메모장에 포커스 있으면 계산기 입력 무시 - `display.focus()`를 강제로 호출해서 키보드 입력이 계산기 입력창으로 자동 전달되게. 🎉 셋팅끝!! ~~*이제핑계를다잃엇다..진짜공부해야함*~~


2025-07-24 ⋯ Hugo #4 Markdown HTML 렌더링 문제

1. 문제 Hugo book Theme는 원래 위 코드를 작성하면 아래처럼 토글이 나온다. 토글 토글 내용 어느날부터 갑자기 토글이든 문단나누기든 다 안먹어서, 근데 원인을 몰라서 그냥 shortcode 기능 없는대로 쓰다가, 너무 불편해서 좀 찾아봤고 `hugo.toml`에 다음 내용 넣어준 뒤로는 잘 작동했다. 근데 이후에 html 관련 포스팅을 작성했는데 넣어준 코드가 다 깨졌다. 근데 심지어 html 코드 뿐만아니라 plain text 처리된 코드들도 다 깨졌다. (위에서 보다시피 이 글에선 잘 나오는데..) 실제 호스팅 화면은 이렇게 댓글창도 없어졌고 md 파일은 깨졌다. 2. 해결 md 파일을 봤을때 ``가 들어가고부터 이상해진것같아서 코드로 감싸주니까 정상적으로 바꼈다. 3. 원인 ``가 왜 문제가 되는지 몰라서 찾아봤는데 일단 이 설정에서 `markup.goldmark.renderer.unsafe = true`는 Markdown 안에 작성된 HTML 태그를 그대로 렌더링하도록 허용한다는 의미여서 true 설정하면 로 감싸지 않은 HTML 태그는 실제 요소로 렌더링됨 따라서 unsafe = true 상태에서 코드 블럭 없이 HTML 태그를 작성하면 코드 블럭처럼 보이지 않고 실제로 화면에 렌더링됨, 특히 `


2025-07-23 ⋯ JavaScript #1 쇼핑몰 주문 처리

1. 문제 당신은 온라인 쇼핑몰의 개발자로, 고객 주문을 처리하는 프로그램을 작성하고 있습니다. 주문 처리 과정에서는 여러 조건을 고려해야 합니다. 예를 들어, 상품의 재고 여부, 고객의 회원 등급, 주문 금액, 배송 옵션 등을 확인하여 적절한 메시지와 할인율을 적용해야 합니다. 아래의 세부 조건에 맞도록 JavaScript 함수를 구현하고, 최종 결과를 console.log 또는 alert로 출력해보세요. 세부 조건 상품 재고 확인 - 재고가 1개 이상일 경우: 주문을 진행한다. - 재고가 0개일 경우: 품절 메시지를 출력한다. 회원 등급에 따른 할인율 적용 - VIP 회원: 10% 할인 - Gold 회원: 5% 할인 - 그 외 회원: 할인 없음 주문 금액에 따른 추가 할인 (기본 할인 적용 이후 기준) - 100,000원 이상: 5,000원 추가 할인 - 200,000원 이상: 15,000원 추가 할인 배송 옵션에 따른 배송비 처리 - 배송 옵션이 "fast"인 경우: 배송비 3,000원 추가 - 배송 옵션이 "standard" 또는 미선택인 경우: 배송비 없음 최종 출력 메시지 - 주문이 정상적으로 처리된 경우: "주문이 정상적으로 완료되었습니다." 메시지와 함께 최종 결제 금액 출력 - 재고가 없는 경우: "죄송합니다. 해당 상품은 품절입니다." 메시지 출력 HTML 인터페이스 구현 조건 - 상품 재고와 주문 금액은 숫자만 입력 가능하도록 설정할 것 (type="number" 사용) - 회원 등급과 배송 옵션은 Drop-down List 형태로 구현할 것 (`` 태그 사용) - 버튼 클릭 시 모든 입력값의 유효성을 검사한 후 함수가 호출되도록 할 것 - 함수 실행 결과는 alert()을 사용해 사용자에게 보여줄 것 CSS를 이용하여 페이지의 스타일을 간단히 꾸밀 것 (예: 선택자 `button`, 속성 `background-color`, 값 `green` 등 사용). 2. 구현 코드 3. 결과


2025-07-22 ⋯ HTML #1 프로필 웹페이지 작성

chatgpt로 css 넣은 버전. 챗지피티 돌리니까 확이뻐지긴하지만 그래두... naive 버전이 더 정감가서 좋다.


2025-04-13 ⋯ Hugo #3 블로그 scss 커스텀하기 (visited 링크 글자색 수정)

기존 화면에서 방문하지않은 하이퍼링크는 파란색, 방문한 링크는 보라색으로 표시됐는데, 뭔가 링크를 누르는 느낌보다는 글을 누르는 느낌이 났으면 좋겠어서 + 근데 링크인건 인지돼야해서 적절한 색깔로 바꿔주고 싶었다. Hugo Book Theme 깃히브를 확인해보면 assets 디렉토리에 _variables.scss 파일을 생성해주면 되는듯해서 아래와 같이 넣어줬다. 여러 색깔을 시도한 흔적.. ㅋㅋ 최종적으로 진한 남색으로 선택해줬다! 진한 회색이 자연스럽긴한데 링크 느낌이 안나서 남색으로 설정해줬다 요건 색깔만 봤을땐 이뻐보였는데 적용하니깐 별로였다. cf) _custom.scss랑 _variables.scss랑 뭐가 다른지 모르겠는데 ㅠ custom은 안먹고 variables만 먹음.


2024-12-31 ⋯ Hugo #2 Favicon 변경, Giscus 댓글창 추가

1. Favicon 변경 Hugo-book 테마의 github에서 README 파일을 읽어보면, logo와 favicon 이미지의 경로 정보를 찾을 수 있다. (logo 정보) (favicon 정보) 확인 결과 `static` 디렉토리에 각각 `logo.png`, `favicon.png`로 저장해두면 반영되는것 같다. 참고로 Hugo-book 테마의 오리지널 웹사이트는 아래와 같이 디자인되어있고 최상단 탭에 들어가는 이미지가 `logo.png`, 블로그 이름 옆에 들어가는 이미지가 `favicon.png`이다. 먼저 `static` 디렉토리에 넣고 싶은 로고와 파비콘을 `logo.png`, `favicon.png` 로 저장해준다. 다음으로, hugo.toml 파일을 열어 아래 내용을 추가해준다. 블로그를 들어가보면 설정한 로고와 파비콘이 잘 들어간것을 확인할 수 있다! 2. Giscus 댓글창 추가 Giscus 댓글 시스템을 Hugo 기반 블로그에 연동하기 위해서는 Giscus에 블로그 리포지토리를 연결한 후, js script를 작성하여 블로그 리포지토리의 layouts 디렉토리에 저장하면 된다고 한다. 이때 연결할 리포지토리는 다음 3가지 조건을 만족해야 한다. 1. Public이어야 함. 2. giscus 앱이 설치되어 있어야 함. 3. Discussions 기능이 해당 저장소에서 활성화되어 있어야 함. 2-1. 공개 저장소 확인 블로그 리포지토리의 Settings > General의 맨 하단을 보면 Danger Zone에서 public인지 private인지 확인이 가능하다. public이므로 다음으로 넘어간다. 2-2. Giscus 앱 설치 https://github.com/apps/giscus 에 접속하여 install, configure를 진행하면 쉽게 설치된다. Repository access는 All repositories 로 설정했다. 2-3. Discussion 기능 활성화 블로그 리포지토리의 Settings > General을 스크롤해보면 Discussions 체크박스가 생긴 것을 확인할 수 있다. 이를 체크해준다. 위로 스크롤해보면 상단에 Discussions 탭이 생겼다. 이제 블로그 리포지토리가 Giscus에 연결할 3가지 조건을 만족하였고 블로그를 Giscus로 연결해주면 된다. 연결해주려면 아래 형식의 js 스크립트를 작성하여 layouts/partials/comments.html에 추가해주면 된다. js 스크립트는 https://giscus.app/ko에서 파라미터를 선택하면 적절하게 생성해준다! 해당 내용을 복사해서 블로그 리포지토리의 layouts/partials/docs/comments.html로 생성해주었다. 성공적으로 댓글창이 추가되었다!! 3. 참고한 블로그 https://parker1609.github.io/post/creating-my-blog-with-hugo/