기술면접/JAVA 관련 기술면접

[JAVA 기술면접] 객체지향 특징 (4가지 특징, SOLID)

거북이의 기술블로그 2024. 10. 24. 17:07

 

객체지향이란?

  • 필요한 데이터를 추상화 시켜서 상태와 행위를 가진 객체를 만들어서 사용
  • 절차적인 것과 달리, 객체들을 이용하여 유기적인 상호작용을 통해서 로직을 구성
장점
- 코드 재사용성 용이
- 유지보수의 편리함
- 대형 프로젝트에 적합

단점
- 처리속도가 상대적으로 느림
- 객체가 많으면 용량이 커짐
- 설계시 많은 시간과 노력이 필요

 

객체지향의 4가지 특징

  • 캡슐화
    • 접근제어자를 이용하여, 객체 안에 노출되어야할 것과 노출되지 말아야할 것을 정의하고 접근을 제어하여 은닉하는 것이 목적
    • 코드의 수정이 있을때도, 영향범위를 예측할 수 있어 유용함
  • 상속
    • 부모 객체의 속성과 기능을 이어받아 사용이 가능
    • 필요에 따라, 자식객체에서만 특정하여 수정이 가능
  • 추상화
    • "공통의" 속성이나 기능을 묶어서 표현
    • 주로 추상화클래스 혹은 인터페이스를 이용하여 구현
추상클래스
- 추상클래스의 경우,  인스턴스를 생성할 수 없으며 하나 이상의 추상 메서드를 가질 수 있음
- 추상메서드의 경우 자식클래스에서 구현이 되어야함 (필수)
  • 다형성
    • 하나의 변수명, 함수명 등이 상황에 따라 다른의미로 해석
    • Override , Overload 을 의미함
Override : 메소드를 재정의 하는 것
Overload : 매개변수의 타입과 개수를 다르게하여 매개변수에 따라 다르게 호출

 

public abstract class Animal{
    
    // 캡슐화 (private 접근제어자 사용)
    private String name;
    
    public Animal(String name){
        this.name =name;
    }
    
    // 추상메서드 (추상화)
    public abstract void makeSound();
    
    // 일반메서드
    public void sleep(){
        System.out.println(name + "is sleep");
    }
    
    // 캡슐화 (name 접근)
    public String getName(){
        return name;
    }
}

// 상속
class Dog extends Animal{
     
     public Dog(String name){
         super(name);
     }
     
     // 다형성
     @Override
     public void makeSound(){
         System.out.println("월월!");
     }
}

// 상속
class Cat extends Animal{
     
     public Cat(String name){
         super(name);
     }
     
     // 다형성
     @Override
     public void makeSound(){
         System.out.println("야옹!");
     }
}

public class Zoo{
    public static void main(String[]args){
        
        // 다형성 : 부모클래스 타입으로 객체 참조
        Animal dog = new Dog("개");
        Animal cat = new Cat("고양이");
        
        dog.makeSound();
        cat.makeSound();
     }
}

ㅈㅈ

 

객체 지향 설계 5대 원칙 (SOLID)

  • SRP 단일 책임 원칙(Single Responsibility Principle)
    • 한 클래스는 하나의 책임만 가져야함
    • 여러개의 기능들을 한 클래스 내부에 다 구현한다면 SRP를 위반한 것
  • OCP 개방 폐쇄 원칙(Open/Closed Principle)
    • 확장에는 열려있으나, 변경에는 닫혀있어야함
    • 구현체마다 변경해야할 메서드가 있다면, 인터페이스를 활용하여 변경을 최소화해야함
    • (새롭게 만드는 것은 변경이 아님)
  • LSP 리스코프 치환 원칙 (Liskov substitution Principle)
    • 자식 클래스가 부모클래스로 완벽하게 대체될 수 있어야함
    • 부모클래스의 구현에 경우 더하는 메서드를 구현을 했는데, 자식클래스에서 동일한 메서드를 빼는 기능을 구현한다면 서로 대체될 수 가없음 (리스코프 위반)
  • ISP 인터페이스 분리 원칙 (Interface segregation principle)
    • 인터페이스 한개로 다 처리하지 말고, 여러개의 인터페이스로 분리
    • 클라이언트들이 꼭 필요한 메서드만 이용이 가능해짐
    • 인터페이스를 분리하게 되면, 대체 가능성이 높아짐
  • DIP 의존관계 원칙 (Dependency inversion principle)
    • 추상화에 의존해야지, 구체화에 의존하면 안됨
    • 구현클래스에 의존하지 말고, 인터페이스에 의존해야함

 

// ISP : 알림기능만 하는 인터페이스
Interface Notifier{
    void send(String message);
}

class Email implements Notifier{
    @Override
    public void send(String message){
         System.out.println("sending email: " +message);
    }
}

class Sns implements Notifier{
    @Override
    public void send(String message){
         System.out.println("sending Sns: " +message);
    }
}

//SRP : 메시지 관리 클래스 (기능 1개)
class Message{
    
    private String content;
    
    public Message(String content){
        this.content = content;
    }
    
    public String getContent(){
        return content;
    }
}

class NotifierService{
     private Notifier notifier;
     
     // DIP 추상화에 의존
     public NotifierService(Notifier notifier){
         this.notifier = notifier;
     }
     
     public void notify(Message message){
         //LSP 원칙: 부모 타입(Notifier)에 의존해 자식 타입(Email, Sns)을 치환 가능
         notifier.send(message.getContent());
     }
}

public class Main{
    public static void main(String[] args){
         Message message = new Message("hello");
         
         Notifier email = new Email(); // OCP 쉽게 확장 가능
         // Notifier sns = new Sns();
         
         NotifierService service = new NotifierService(email);
         service.notify(message);
     }
}