✔️ 이 글은 [스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 김종민] 도서를 바탕으로 정리한 글입니다.
IoC/DI - 제어의 역전/의존성 주입
프로그래밍에서 의존성이란?
A가 B를 의존한다는 건 의존대상B가 변하면 A에 영향을 미친다는 의미이다. 결론적으로 전체가 부분에 의존한다고 할 수 있다.
더 깊이 들어가면 전체와 부분인 객체 사이에 집합 관계(Aggregation)와 구성 관계(Composition)로 구분할 수 있다.
- 집합 관계 : 부분이 전체와 다른 생명 주기를 가질 수 있다. ex) 집과 냉장고
- 구성 관계 : 부분이 전체와 같은 생명 주기를 가진다. ex) 사람과 심장
앞으로 들 예시
- Dirver : 운전자 클래스
- Car : 자동차 클래스
- Tire : 타이어 인터페이스
- KoreaTire, AmericaTire : 각 브랜드의 타이어 클래스
직접 의존성을 해결할 때의 예시를 보면 Car 객체가 Tire를 직접 생산하여 Tire에 대한 의존성을 자체적으로 해결하는 방식이 있다.
운전자가 자동차를 생산한다.
자동차는 내부적으로 타이어를 생산한다.
Car car = new Car();
public Car() { tire = new KoreaTire();}
스프링 없이 의존성 주입하기 1 - 생성자를 통한 의존성 주입
운전자가 타이어를 생산한다.
운전자가 자동차를 생산하면서 타이어를 장착한다.
Tire tire = new KoreaTire();
Car car = new Car(tire);
외부에서 생산된 Tire 객체를 Car 생성자의 인자로 주입하는 형태로 구현해서 Car가 구체적으로 어떤 타이어를 호출할지 정해주던 예전 방식에서 Driver가 생산할 타이어를 정하는 방식으로 변경해줄 수 있다. 어떤 타이어를 장착할지 자동차에서 고민할 필요 없이 자동차를 사용하는 운전자가 고민하면 된다.
직접 의존성을 해결할 때에는 Car는 KoreaTire, AmericaTire에 대해 정확히 알고 있어야만 그에 해당하는 객체를 생성할 수 있었는데, 의존성 주입을 적용할 경우 Car는 그저 Tire 인터페이스를 구현한 어떤 객체가 들어오기만 하면 정상적으로 작동하게 된다.
의존성 주입시 확장성도 좋아지는데, 나중에 다른 브랜드의 타이어 클래스가 생겨도 각 타이어 브랜드들이 Tire 인터페이스만 구현한다면 Car 클래스를 변경하지 않고도 사용할 수 있다.
스프링 없이 의존성 주입하기 2 - 속성을 통한 의존성 주입
운전자가 타이어를 생산한다.
운전자가 자동차를 생산한다.
운전자가 자동차에 타이어를 장착한다.
Tire tire = new KoreaTire();
Car car = new Car();
car.setTire(tire);
운전자가 원할 때 Car의 Tire를 교체해줄 수 있도록 속성을 통한 의존성 주입이 필요하다. Driver가 타이어 생산과 자동차 생산까지 해주고, 생산한 자동차에 생산한 타이어를 장착하는 일까지 수행하게 해준다.
Car에 있던 생성자는 없애고, tire 속성에 대한 getter/setter 메소드를 만들어 준다. Driver에서는 원하는 타이어 객체를 생성하고, 기본 생성자로 Car 객체를 만들어준 뒤 setter 메소드를 이용하여 만들어둔 타이어 객체를 넘겨줌으로써 자동차에 타이어를 장착시켜준다.
스프링을 통한 의존성 주입 - XML 파일 사용
운전자가 종합 쇼핑몰에서 타이어를 구매한다.
운전자가 종합 쇼핑몰에서 자동차를 구매한다.
운전자가 자동차에 타이어를 장착한다.
ApplicationContext context = new ClassPathXmlApplicationContext("expert002.xml", Driver.class);
Tire tire = (Tire)context.getBean("tire");
Car car = (Car)context.getBean("car");
car.setTire(tire);
스프링을 통한 의존성 주입은 생성자를 통한 의존성 주입과 속성을 통한 의존성 주입을 모두 지원하는데, 여기서는 속성을 통한 의존성 주입만 살펴본다.
종합 쇼핑몰은 스프링 프레임워크를 의미하고, 스프링을 도입한다고 해서 기존의 방식에서 크게 바뀌지 않고 타이어와 자동차를 생산하는 것에서 구매하는 것으로 바뀌는 정도의 변화만 있다.
Driver 클래스
package expert002;
// 스프링 프레임워크에 대한 정보를 가지고 있는 패키지
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Driver {
public static void main (String[] args) {
// 스프링 프레임워크에 대한 정보
Application Context context = new ClassPathXmlApplicationContext("expert002/expert002.xml");
// 종합 쇼핑몰(스프링 프레임워크)에서 Car와 Tire 구매
Car car = context.getBean("car", Car.class);
Tire tire = context.getBean("tire", Tire.class);
car.setTire(tire); // 타이어 장착
System.out.println(car.getTireBrand());
}
}
expert002.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instacne"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 상품 목록 등록 -->
<bean id="tire" class="expert002.KoreaTire"></bean>
<bean id="americaTire" class="expert002.AmericaTire"></bean>
<bean id="car" class="expert002.Car"></bean>
</beans>
- 상품 등록시 bean 태그 사용해 각 상품을 구분하기 위한 id 속성과 그 상품을 어떤 클래스를 통해 생산(인스턴스화)해야 할지 나타내는 class 속성 지정
- 사용할 클래스 내에서 getBean(아이디, 클래스이름.class)를 통해 상품을 구매(생산, 인스턴스화)할 수 있음
스프링을 도입하게 되면 자동차의 타이어 브랜드를 변경할 때 그 무엇도 재컴파일/재배포 하지 않아도 XML 파일만 수정하면 된다는 이득이 있다.
스프링을 통한 의존성 주입 - 스프링 설정 파일 (XML)에서 속성 주입
운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.
ApplicationContext context = new ClassPathXmlApplicationContext("expert003/expert003.xml");
Car car = context.getBean("car", Car.class);
<bean id="koreaTire" class="expert003.KoreaTire"></bean>
<bean id="americaTire" class="expert003.AmericaTire"></bean>
<bean id="car" class="expert003.Car">
<property name="tire" ref="koreaTire"></property>
</bean>
자바에서 getter/setter 메소드를 속성메소드라고 하는데 xml에서 이 메소드를 대체할 때 property 태그를 사용해준다.
이 방법에서도 Driver 클래스와 xml 파일 외엔 변하는 파일이 없다. Driver 클래스에는 타이어를 생성해주는 코드와 타이어를 장착해주는 코드가 없어지고, xml 파일에는 타이어를 생성하고 장착해주는 코드가 들어간다.
ref="koreaTire"는 코리아타이어를 자동차의 타이어 속성에 결합하는 부분이고, property 태그와 name="tire"는 car의 tire 속성을 설정하는 부분이다.
JUnit 테스트 케이스 코드(CarTest.java)
package expert003;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContestConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("expert003.xml")
public class CarTest {
@Autowired
Car car;
@Test
public void 자동차_코리아타이어_장착_타이어브랜드_테스트() {
assertEquals("장착된 타이어: 코리아 타이어", car.getTireBrand());
}
}
스프링을 통한 의존성 주입 - @Autowired를 통한 속성 주입
운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.
의사코드는 위와 동일한데, @Autowired 어노테이션을 사용해 속성을 주입해주는 방법도 있다. 해당 어노테이션을 사용하기 위해서는 import org.springframework.beans.factory.annotation.Autowired;를 해주면 된다. @Autowired를 사용하면 setter 메소드를 사용하지 않고도 스프링 프레임워크가 설정 파일을 통해 setter 메소드 대신 속성을 주입해 준다.
xml 파일의 beans 태그 안에 xmlns:context="http://www.springframework.org/schema/context"와 xsi:뒤에 http://www.pringframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd를 추가해주고 <context:annotation-config/>태그를 추가해준다. 그리고 이전과는 달리 자동차에 타이어를 장착해주는 property 태그를 없애준다. @Autowired가 이 property 태그을 대신해 car에 property를 자동으로 엮어준다.
자바코드에서는 Car 클래스에서 Tire 객체를 생성하는 코드 위에 @Autowired 어노테이션을 추가해준다.
번외 경기 1. AmericaTire로 변경된 Dirver.java를 실행하려면 어디를 고쳐야 할까?
- 재컴파일할 필요없이 xml파일에서 bean의 id 속성만 바꿔주면 된다.
-
<bean id="tire02" class="expert004.KoreaTire"></bean> <bean id="tire" class="expert004.AmeriacTire"></bean>
번외 경기 2. 위 번외 경기1에서 KoreaTire 부분을 완전히 삭제하고, AmericaTire의 id 속성을 삭제해보자.
- 기존에는 Car.java에서 @Autowired가 지정된 tire 속성과 xml 파일에서 bean의 id 속성이 일치하는 것을 찾아 매칭
- @Autowired를 사용하기 위해 xml에 추가한 부분과 KoreaTire 부분을 삭제해도 정상적으로 구동되는데 그 이유는 @Autowired가 type 기준 매칭을 하기 때문이다. 만약 같은 타입을 구현한 클래스가 여러개 있다면 bean의 id로 구분을 해줘야 하지만, 하나만 존재하는 경우는 인터페이스를 구현한 하나의 클래스 타입으로 매칭된다.
- 스프링에서는 유일한 빈을 선택할 수 없는 경우에는 실행시 에러를 발생시킨다.
- @Autowired는 id보다 type 매칭을 더 우선시하기 때문에 유일한 type이 매칭되거나 유일한 id가 매칭되면 정상적으로 구동된다.
스프링을 통한 의존성 주입 - @Resource를 통한 속성 주입
운전자가 종합 쇼핑몰에서 자동차를 구매 요청한다.
종합 쇼핑몰은 자동차를 생산한다.
종합 쇼핑몰은 타이어를 생산한다.
종합 쇼핑몰은 자동차에 타이어를 장착한다.
종합 쇼핑몰은 운전자에게 자동차를 전달한다.
의사코드는 이번에도 위와 동일하다. @Autowired로 자동 속성 주입을 해주었던 부분의 어노테이션을 @Resource로 바꿔줘도 똑같이 동작한다. @Autowired는 스프링의 어노테이션으로 type이 id보다 우선순위가 높고, @Resource는 자바 표준 어노테이션으로 id가 type보다 우선순위가 높다.
- @Autowired와 @Qualifer를 조합해서 사용하기
- @Resource에서 id가 필드 이름과 다른 빈을 이용해 속성 주입하기
- 자바 어노테이션 표준 JSR-250과 JSR-330
스프링을 통한 의존성 주입 - @Autowired vs. @Resource vs. <property> 태그
@Autowired vs @Resource
출처 | 스프링 프레임워크 | 표준 자바 |
소속 패키지 | org.springframework.beans.factory.annotation.Autowired | javax.annotation.Resource |
빈 검색 방식 | byType먼저 | byName 먼저 |
특이사항 | @Qualifier("") 협업 | name 어트리뷰트 |
byName 강제하기 | @Autowired @Qualifier("tire1") | @Resource(name="tire1") |
사례 연구 1. XML 설정 - 한 개의 빈이 id 없이 tire 인터페이스를 구현한 경우
<bean class="expert006.KoreaTire"></bean> 태그만 있고
Car 클래스 안 Tire tire;에 @Resource 어노테이션을 붙이거나 @Autowired를 붙여도 두 경우 모두 정상 구동된다.
사례 연구 2. XML 설정 - 두 개의 빈이 id 없이 tire 인터페이스를 구현한 경우
두 개의 빈이 id가 설정되어 있지 않고 @Resource나 @Autowired 어노테이션을 사용하면 하나의 빈을 특정할 수 없기 때문에 에러가 발생한다.
사례 연구 3. XML 설정 - 두 개의 빈이 tire 인터페이스를 구현하고 하나가 일치하는 id를 가진 경우
@Resource, @Autowired 모두 정상 작동한다.
사례 연구 4. XML 설정 - 두 개의 빈이 tire 인터페이스를 구현하고 일치하는 id가 없는 경우
일치하는 id가 없는 경우 @Resource나 @Autowired 어노테이션을 사용하면 하나의 빈을 특정할 수 없기 때문에 에러가 발생한다.
사례 연구 5. XML 설정 - 일치하는 id가 하나 있지만 인터페이스를 구현하지 않은 경우
@Resource를 사용하면 일치하는 name이 없다는 오류 메시지를, @Autowired를 사용하면 일치하는 type이 없다는 오류 메시지를 출력한다.
유지보수에 무관한 의존 관계라면 @Resource를, 유지보수와 밀접하거나 자주 변경되는 관계라면 <property> 태그를 사용하는 것이 좋다.
사례 연구 6. XML 설정 - 두 개의 빈이 tire 인터페이스를 구현하고 속성과 일치하는 id가 없지만 @Resource 어노테이션의 name 속성이 id와 일치하는 경우
@Resource에 소괄호를 써줘서 그 안에 name 속성을 지정해 일치하는 id가 있다면 정상 작동한다.
사례 연구 7. 사례연구 6과 같도록 @Autowired를 지정하려면 다음과 같이 설정한다.
@Autowired 어노테이션 밑에 @Qualifier("name")을 써주면 된다.
변수에 값을 할당하는 모든 곳에 의존 관계가 생긴다. 의존 관계는 외부에 있을수도, 내부에 있을 수도 있다. DI는 외부에 있는 의존 대상을 주입하는 것을 말한다. 의존 대상을 구현하고 배치할 떄는 SOLID와 응집도는 높이고, 결합도는 낮추라는 기본 원칙에 충실해야 유지보수가 수월해진다.
AOP - Aspect? 관점? 핵심 관심사? 횡단 관심사?
AOP는 Aspect-Oriented Programming의 약자로 관점 지향 프로그래밍을 의미한다.
DI가 의존성(new) 주입이라면 스프링 AOP는 로직(code) 주입이라고 할 수 있다.
프로그램을 작성하다보면 다수의 모듈에 공통적으로 나타나는 부분이 존재하는데, 이를 횡단 관심사(cross-cutting concern)라고 한다. 그리고 각 기능에 따라 다르게 나타나는 부분을 핵심 관심사라 해서 코드 = 횡단 관심사 + 핵심 관심사로 이루어져 있다.
메소드 내에 코드를 주입할 수 있는 곳은 Around, Before, After, AfterRunning, AfterThrowing으로 총 5군데이다.
예시 코드
Boy.java
package aop002;
public class Boy implements Person {
public void runSomething() {
System.out.println("컴퓨터로 게임을 한다.");
}
}
Girl.java
package aop002;
public class Girl implements Person {
public void runSomething() {
System.out.println("요리를 한다.");
}
}
Start.java
package aop002;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Start {
public static void main (String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop002.xml", Start.class);
Person romeo = context.getBean("boy", Person.class);
Person juliet = context.getBean("girl", Person.class);
romeo.runSomething();
juliet.runSomething();
}
}
Person.java
package aop002;
public interface Person {
void runSomething();
}
MyAspect.java
package aop002;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspect {
@Before("execution(* runSomething())")
public void before(JoinPoint joinPoint) {
System.out.println("얼굴 인식 확인 : 문을 개방하라");
}
}
aop002.xml
<?xml version="1.0" encoding ="UTF-8"?>
<beans
xmlns = " "
xmlns:xsi = " "
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
">
<aop:aspectj-autoproxy />
<bean id="myAspect" class="aop002.MyAspect" />
<bean id="boy" class="aop002.Boy"/>
<bean id="girl" class="aop002.Girl"/>
</beans>
Boy.java와 Girl.java에서 횡단 관심사는 사라지고, AOP가 인터페이스 기반으로 동작하기 때문에 Person.java라는 파일이 만들어졌다. 또 없어진 횡단 관심사 처리는 MyAspect.java에서 처리한다.
xml파일에서 만들어진 빈들은 객체의 생성과 의존성 주입을 스프링 프레임워크에게 위임하기 위해서 선언되었는데, boy와 girl 빈은 AOP 적용 대상이고, myAspect 빈은 AOP의 Aspect이기 때문에 등록되었다.
<aop:aspectj-autoproxy /> 는 proxy패턴을 이용해 횡단 관심사를 핵심 관심사에 주입하는 AOP 프록시 자동 사용을 명시하는 태그이다. 호출하는 쪽에서 romeo.runSomething()을 호출하면 프록시가 그 요청을 받아 진짜 romeo 객체에게 요청을 전달하는 역할을 한다. 프록시 내에서 연결 역할을 하는 runSomething() 메소드는 요청을 전달도 하고, 주고 받는 내용을 감시하거나 조작할 수도 있다.
스프링 AOP에서는 호출하는 쪽에서나 호출 당하는 쪽에서나 그 누구도 프록시가 존재하는지 조차도 모른다. 오직 스프링 프레임워크에서만 프록시의 존재를 안다. 버퍼도 일종의 프록시이고, 캐시 서버도 프록시의 한 예이다.
Pointcut- Aspect 적용 위치 지정자
@Before 어노테이션 괄호 안에 있는 * runSomething()을 Pointcut이라고 부른다. 이 Pointcut으로 지정한 메소드가 실행되기 전(@Before)에 해당 메소드를 실행하라는 의미를 갖는다.
결국 Pointcut은 횡단 관심사를 적용할 타깃 메소드를 선택하는 지시자로 Aspect 적용 위치 지정자라고 할 수 있다. 스프링 AOP에서는 메소드에만 Aspect를 적용할 수 있지만 AspectJ처럼 다른 AOP 프레임워크에서는 속성에도 Aspect를 적용할 수 있어 Aspect 적용 위치 지정자라고 하는 것이 맞는 표현이다. Pointcut을 메소드 선정 알고리즘이라고도 한다.
타깃 메소드 지정자의 정규식
[접근제한자패턴] 리턴타입패턴 [ 패키지&클래스패턴] 메소드이름패턴(파라미터패턴) [throws 예외패턴]
[]안은 생략 가능
*은 리턴타입이 무엇이든 상관없다는 의미이다.
JoinPoint - 연결 가능한 지점
JointPoint는 Aspect 적용이 가능한 모든 지점을 의미한다. Aspect를 적용할 수 있는 지점 중 일부가 Pointcut이라 Pointcut은 Aspect의 부분집합이라 할 수 있다.
- 광의의 JoinPoint : Aspect 적용이 가능한 모든 지점
- 협의의 JoinPoint : 호출된 객체의 메소드
JoinPoint 파라미터를 사용하면 실행 시점에 실제 호출된 메소드가 무엇인지, 실제 호출된 메소드를 소유한 객체가 무엇인지, 또 호출된 메소드의 파라미터는 무엇인지 등의 정보를 확인할 수 있다.
Advice - 언제, 무엇을
Advice란 Pointcut에 언제, 무엇을 적용할지 정의한 메소드이다. 타깃 객체의 타깃 메소드에 적용될 부가 기능이라고 표현하기도 한다. @Before가 지정된 메소드가 Advice로 Pointcut이 시작되기 전에 해당 메소드를 실행하라는 의미를 담고 있다.
Aspect - Advisor의 집합체
Aspect = 여러개의 Pointcut + 여러개의 Advice
Aspect는 언제, 무엇을, 어디에라는 정보를 모두 담고 있다.
Advisor - 어디서, 언제 무엇을
Advisor = 하나의 Pointcut + 하나의 Advice
Advisor는 언제, 무엇을, 어디에라는 정보를 담고 있다는 점에서는 Aspect와 동일하지만 하나씩 결합한다는 점에서 다르다.
Advisor는 스프링 AOP에서만 사용하는 용어이며, Aspect가 나온 뒤로는 거의 안쓰인다.
POJO와 XML 기반 AOP
기존 어노테이션 기반으로 작성한 AOP 예제를 POJO와 XML 기반으로 변경하기 위해서는 Aspect가 정의돼 있는 MyAspect.java와 XML 파일을 수정해야 한다.
MyAspect.java
package aop003;
import org.aspectj.lang.JoinPoint;
public class MyAspect {
public void before(JoinPoint joinPoint) {
System.out.println("얼굴 인식 확인 : 문을 개방하라");
}
}
@Before와 @Aspect가 사라져 MyAspect.java는 스프링 프레임워크에 의존하지 않는 POJO가 된다.
aop003.xml
<?xml version="1.0" encoding ="UTF-8"?>
<beans
...>
<aop:aspectj-autoproxy />
<bean id="myAspect" class="aop002.MyAspect" />
<bean id="boy" class="aop002.Boy"/>
<bean id="girl" class="aop002.Girl"/>
<aop:config>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut="execution(*runSomething())"/>
</aop:aspect>
</aop:config>
</beans>
MyAspect에서 사라진 어노테이션들이 xml파일에 태그로 표현되었다.
AOP 기초 완성
After 어드바이스를 추가하려면 xml 파일에 <aop:after method="lockDoor" pointcut="execution(* runSomething())"> 태그를 추가해주면 된다.
Before 어드바이스와 After 어드바이스에 해당하는 태그에 pointcut 부분이 중복되는데 이걸
<aop:pointcut expression="execution(* runSomething())" id="iampc"/>
로 지정해줘서 Before와 After 태그의 pointcut를 pointcut-ref="iampc"로 변경해줄 수 있다.
어노테이션을 사용한 경우에는 private void iampc(){} 메소드에 @Pointcut("execution(* runSomething())")을 써주고 @Before("iampc()") @After("iampc()")로 사용해주면 된다.
PSA - 일관성 있는 서비스 추상화
PSA(Portable Service Abstraction)은 일관성 있는 서비스 추상화이다.
서비스 추상화는 어댑터 패턴을 적용해 같은 일을 하는 다수의 기술을 공통의 인터페이스로 제어할 수 있게 해주는 것을 의미한다. 스프링 프레임워크는 서비스 추상화를 위해 다양한 어댑터를 제공하는데 OXM(Object XML Mapping: 객체와 XML 매핑) 기술만 하더라도 Castor, JAXB, XMLBeans, JiBX, XStream 등 다양한 기술이 있는데 이 다양한 기술들이 제공하는 API는 제각각이다. 스프링은 이 제각각인 API를 위한 어댑터를 제공해 실제로 어떤 OXM 기술을 쓰든 일관된 방식으로 코드를 작성할 수 있게 해준다.
이처럼 서비스 추상화를 해주면서 일관성 있는 방식을 제공한다고 해서 PSA라고 한다.
스프링 PSA 예시로는 OXM, ORM, 캐시, 트랜잭션 등이 있다.
'도서 > 스프링 입문을 위한 자바 객체 지향의 원리와 이해' 카테고리의 다른 글
[도서/스프링 입문] #6 스프링이 사랑한 디자인 패턴 (0) | 2023.01.17 |
---|---|
[도서/스프링 입문] #5 객체 지향 설계 5원칙 - SOLID (1) | 2023.01.17 |
[도서/스프링 입문] #4 자바가 확장한 객체 지향 (0) | 2023.01.14 |
[도서/스프링 입문] #3 자바와 객체 지향 (0) | 2023.01.14 |
[도서/스프링 입문] #2 자바와 절차적/구조적 프로그래밍 (0) | 2023.01.14 |