결합도는 낮추고 응집도를 높여야 한다.
결합도 : 클래스 간에 얼마나 많이 의존하고 있는가를 나타낸다. 상호 의존도가 낮을수록 코드의 유지・보수가 유리해진다. 끈끈이가 서로 붙듯이 결합도 높은 클래스 간 강하게 연결되어 있다면 떼어낼 때 끈끈이를 완전히 제거해야 되는 것처럼 코드를 갈아 엎어야 될 수 있을것이다. 포스트 잇처럼 필요에 의해서 붙였다 쉽게 땔 수 있어야 한다.
응집도 : 얼마나 많이 단일 클래스에서 책임(기능)을 가지고 있는지 나타낸다. 단일 클래스에 하나의 책임을 가지면 응집도가 가장 높은 상태에 해당되며 이 역시 유지・보수에 유리하다. 응집도가 낮은 상태는 서로 관련 없는 기능들이 하나의 클래스에 집중되어 작성된 상태이다.
class Student
// B 클래스가 student 클래스에 강하게 결합된 경우
class B {
private Student student;
public B(Student student) {
this.student = student;
}
public void 데이터_조작하기() {
// B 클래스가 A 클래스의 내부 구현에 강하게 의존하고 있음
int value = student.getData();
value *= 2;
}
}
class Student // 한 학생의 정보를 저장하는 클래스
class StudentDatabase // student들을 List로 저장하는 클래스
class StudentFileManager //student List를 파일에 저장하는 클래스
어떤 클래스의 변수나 메소드를 직접적으로 사용하는 경우 강하게 결합되어 있으며 제일 좋지 않은 결합 방식이다.
여러 클래스가 하나의 전역 변수(static)에 강하게 결합되어 있는 경우
외부의 클래스에서 한 클래스의 제어를 넘겨주는 것, flag을 설정하여 true일 경우와 false 일 경우 다른 동작을 지시할 수 있다.
클래스의 매개변수로 배열이나 오브젝트, 스트럭처가 전달되어 실행되는 방식
순수한 데이터 요소를 전달받아 실행하도록 하는 방식이다.
클래스가 import를 통해서 연결되거나 구체적인 클래스를 통해서 연결될 때
SummaryReport summaryReport = new SummaryReport();
ReportManager reportManager = new ReportManager(summaryReport);
순수하게 데이터를 통해서 객체간 통신하는 방식, 메시지를 통해서만 상호작용한다.
Report summaryReport = new SummaryReport();
Report detailReport = new DetailReport();
Report customReport = new CustomReport("This is a custom report.");
class Utility{
public void addStudent(String name, int age) // 학생 추가
public List<Student> getStudents() // 학생 List 받기
public void saveToFile(String filename) throws IOException //학생 데이터를 파일에 저장하기
public void saveToDatabase() //학생 데이터를 데이터베이스에 저장하기
public void deleteFromDatabase(String name) //데이터베이스에서 학생 데이터 삭제하기
public void printStudents() // 학생들읠 목록을 출력하기
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Student 클래스 안에 관련된 기능만 추가하였다.
다른 프로그램에서 재사용이 용이하다.
코드 변경시 다른 클래스에 영향을 끼치지 않는다.
우연 응집도에 가까운 클래스는 낮은 응집도를 가지고 있으며 기능 응집도에 가까울 수록 높은 응집도를 가지게 된다.
클래스 안 구성 요소들이 우연히 같이 있는 경우(서로 관련이 하나도 없음)
논리적으로 비슷한 , 관련된 기능들이 한 클래스에 모여 있지만 서로 관계가 밀접하지 않는 경우
특정 시점에 수행해야 하는 기능들이 한 클래스에 모여 있는 경우
서로 연관이 적을 수 있다.
특정 순서로 수행되는 기능들이 한 클래스에 응집되어 있는 경우
하나의 클래스에 있는 메소드들을 여러 개 호출하는 경우
동일한 데이터를 입력받아 다르게 수행하는 기능들이 모여있다.
같은 파일에서 데이터를 읽고, 데이터를 처리하며, 데이터를 출력하는 작업이 한 모듈에 포함된 경우
한 기능의 출력이 다른 기능의 입력값이 되어 그 기능을 수행하는 경우
데이터 입력, 데이터 처리, 데이터 출력이 순차적으로 연결된 모듈.
한 클래스의 기능들이 동일한 목표를 위해 수행되는 경우에 해당된다.
가장 높은 응집도. 모듈이 하나의 기능에 집중되어 있어 이해와 유지보수가 용이
기존의 코드를 변경하지 않으면서 확장에는 열려있어야 한다. 즉 수정은 폐쇄, 확장은 개방 되어야 한다. 확장은 새로운 기능의 추가를 의미한다.
인터페이스나 추상 클래스로 상속받아 구현된 클래스를 주입함으로써 구현됨
OCP 원칙을 따른 JDBC 인터페이스, Mybatis, Hibernate 등에서 찾아 볼 수 있다.
새로운 요구사항이나 변경사항이 발생할때마다 내부의 코드를 변경하면 버그 발생 가능성을 높이게 된다. 이때 중간에 인터페이스나 추상클래스를 두고 그것을 상속・구현한 클래스를 주입하는 방식으로 확장성을 향상시키고 유지・보스를 용이하게 한다.
클래스를 주입하는 방식은 DIP(의존 역전 원칙)과 관련이 있다.
interface MessagePrinter {
void print(String message);
}
class A_MessagePrinter implements MessagePrinter {
@Override
public void print(String message) {
System.out.println("A: " + message);
}
}
class B_MessagePrinter implements MessagePrinter {
@Override
public void print(String message) {
System.out.println("B: " + message);
}
}
public class Main {
public static void main(String[] args) {
MessagePrinter aPrinter = new AMessagePrinter();
MessagePrinter bPrinter = new BMessagePrinter();
}
}
객체지향 설계 원칙과 관련된 그레디 부치(Grady Booch)의 조언
1. 추상화란 다른 모든 종류의 객체로부터 식별될 수 있는 객체의 본질적인 특징이다.
2. 객체 지향 프로그래밍의 목적은 소프트웨어 구성 요소의 재사용을 촉진하는 것이다.
3. 좋은 설계는 단순성, 유연성, 견고성의 상충하는 요구 사이에서 적절한 균형을 찾는 것이다.
4. 다형성은 서로 다른 객체들이 동일한 메시지에 대해 각자의 방식으로 응답할 수 있는 능력입니다.
class Bird {
public void fly() {
System.out.println("Bird is flying");
}
}
class Sparrow extends Bird {
@Override
public void fly() {
System.out.println("Sparrow is flying");
}
}
public class Main {
public static void main(String[] args) {
Bird bird = new Sparrow(); // 상위 타입 변수에 하위 타입 인스턴스 할당
bird.fly(); // "Sparrow is flying" 출력
}
}
class Rectangle {
protected int width, height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // 정사각형의 특성에 맞게 높이도 설정
}
@Override
public void setHeight(int height) {
this.height = height;
this.width = height; // 정사각형의 특성에 맞게 너비도 설정
}
}
public class Main {
public static void main(String[] args) {
Rectangle rect = new Square(); // 상위 타입 변수에 하위 타입 인스턴스 할당
rect.setWidth(5);
rect.setHeight(10);
System.out.println(rect.getArea()); // 예상되는 출력값은 50이지만, 실제로는 100
}
}
interface Shape {
double getArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
public class Main {
public static void main(String[] args) {
Shape shape = new Circle(5); // Circle로 교체
System.out.println("Circle Area: " + shape.getArea());
shape = new Rectangle(4, 5); // Rectangle로 교체
System.out.println("Rectangle Area: " + shape.getArea());
}
}
interface Vehicle {
void startEngine();
}
class Car implements Vehicle {
@Override
public void startEngine() {
System.out.println("Car engine started");
}
}
class ElectricCar implements Vehicle {
@Override
public void startEngine() {
System.out.println("Electric Car started silently");
}
}
public class Main {
public static void main(String[] args) {
Vehicle vehicle = new Car();
vehicle.startEngine(); // "Car engine started" 출력
vehicle = new ElectricCar();
vehicle.startEngine(); // "Electric Car started silently" 출력
}
}
class Animal {
public void makeSound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // IS-A 관계
animal.makeSound(); // "Bark" 출력
}
}
interface Engine {
void start();
}
class GasEngine implements Engine {
@Override
public void start() {
System.out.println("Gas engine started");
}
}
class ElectricEngine implements Engine {
@Override
public void start() {
System.out.println("Electric engine started");
}
}
class Vehicle {
private Engine engine;
public Vehicle(Engine engine) {
this.engine = engine;
}
public void startEngine() {
engine.start();
}
}
public class Main {
public static void main(String[] args) {
Vehicle gasVehicle = new Vehicle(new GasEngine());
gasVehicle.startEngine(); // "Gas engine started" 출력
Vehicle electricVehicle = new Vehicle(new ElectricEngine());
electricVehicle.startEngine(); // "Electric engine started" 출력
}
}
class Payment {
public void process(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
// 결제 처리 로직
}
}
class OnlinePayment extends Payment {
@Override
public void process(int amount) {
if (amount < 10) {
throw new IllegalArgumentException("Minimum amount for online payment is 10");
}
// 온라인 결제 처리 로직
}
}
interface Worker {
void work();
void eat();
void sleep();
}
class HumanWorker implements Worker {
public void work() {
// 작업하는 로직
}
public void eat() {
// 식사하는 로직
}
public void sleep() {
// 잠자는 로직
}
}
class RobotWorker implements Worker {
public void work() {
// 작업하는 로직
}
public void eat() {
throw new UnsupportedOperationException("로봇은 먹을 수 없습니다.");
}
public void sleep() {
throw new UnsupportedOperationException("로봇은 잠을 자지 않습니다.");
}
}
interface Workable {
void work();
}
interface Eatable {
void eat();
}
interface Sleepable {
void sleep();
}
class HumanWorker implements Workable, Eatable, Sleepable {
public void work() {
// 작업하는 로직
}
public void eat() {
// 식사하는 로직
}
public void sleep() {
// 잠자는 로직
}
}
class RobotWorker implements Workable {
public void work() {
// 작업하는 로직
}
}
유, 재, 변, 의 에 좋다.
유지 보수성 향상
재사용성 증가
(코드)변경 용이
의존성 감소
SRP는 클래스의 단일 책임 원칙으로서 한 클래스의 기능들이 동일한 목표를 위해 수행되는 경우를 목표로 한다. ISP와 SRP는 결합성 측면에서는 상충되지 않으나 응집도 측면에서 상충될 수 있다. Worker 인터페이스는 work(), eat(), sleep() 으로 메소드가 구성되어 있어 SRP 원칙에는 문제가 없으나 Robot은 eat(),과 sleep()이 필요없으므로 ISP에는 문제가 생길 수 있다.
SRP는 클래스에 명확한 책임을 갖게하여 응집도를 높이고, ISP는 클라이언트에 맞춘 인터페이스를 제공하여 불필요한 의존성을 줄인다.