IOC 

Inversion of Control

제어권이 뒤바뀐다.

원래는 자기가 사용할 의존성은 자기가 만들어 사용한다.

Ex) new로 생성해서 사용하는 것

 

Ioc는 사용하지만 만들지 않는다. 즉 누군가가 생성해준다.

누군가란? 스프링

Ex) new를 사용하지 않는다.

 

의존성 주입 예제

 

스프링에서 의존성을 주입해준다.(생성자 방식)

private final OwnerRepository owners;
public OwnerController(OwnerRepository clinicService, VisitRepository visits) { 
	this.owners = clinicService; 
    //vistis 는 생략 this.visits = visits;
}

 

만약 IoC가 적용 안되었다면?

이런 식으로 사용하면 원래 Null point Exception 에러가 난다.

 

Spring이 관리하는 객체를 Bean이라고 함.

즉, 스프링이 빈 객체를 생성해 주입해줘서 동작되게 한다.

 

결론 : IoC에서 객체 생성은 내가 하는 것이 아닌 스프링이 필요한 의존성을 주입해준다. 

 

IOC 컨테이너(Bean Factory) ApplicationContext

Bean Factory이 IOC 컨테이너인데 ApplicationContext는 BeanFactory를 상속받는다.

 

IOC컨테이너 역할

bean 생성하고 bean들 사이에 의존성을 엮어주고 스프링에 제공해준다.

 

OwnerController, OwnerRepository 등 즉 컨트롤러, 레파지토리 등은 빈으로 등록해 사용한다.

 

모든 클래스가 다 무조건 bean으로 등록되는 것이 아니다.

 

@Controller 등

@으로 등록하는 방식,

extends Repository 상속하는 방식,

@Bean으로 등록하는 방식 등

여러 가지 방법으로 Bean으로 설정이 가능하다.

 

의존성 주입은 bean끼리만 사용한다. 예외의 경우 다른 객체에 할 수 있지만 거의 하지 않는다.

즉, 주로 IOC컨테이너 안에 있는 객체에만 서로 의존성 주입을 한다.

 

IOC컨테이너는 매번 새로 객체를 생성하는 것이 아니라 생성된 객체를 같이 사용한다.

 

Bean

IoC 컨테이너가 관리하는 객체를 말한다. 

ApplicationContext가 만들어서 그 안에 담고 있는 객체를 말한다.

Component Scannig

-@Controller

-@Service

-@Repository

-@Component

등 여러 가지 등을 찾아서 bean으로 등록한다.

 

ex)

public interface OwnerRepository extends Repository<Owner, Integer>

Repository를 보면 특정한 @이 없더라도 특정 인터페이스를 상속받는 클래스를 찾아서 

내부에서 인터페이스를 구현하고 Bean으로 등록한다.

 

직적 Bean으로 등록

@Configuration과 @Bean으로 등록할 수 있다.

@Configuration(proxyBeanMethods = false)

@EnableCaching

class CacheConfiguration {



@Bean

public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {

return cm -> {

cm.createCache("vets", cacheConfiguration());

};

}

}

 

 

@Autowried로 IoC컨테이너에서 객체를 꺼내 사용할 수 있다.

 

 

의존성 주입(Dependency Injection)

 

@Autowried로 보통 주입을 한다.

생성자

스프링 4.3부터 이상부턴 생성자에서 생략하고 사용할 수 있다.

public OwnerController(OwnerRepository clinicService, VisitRepository visits) {

this.owners = clinicService;

this.visits = visits;

}

,

필드

@Autowired

private final OwnerRepository owners;

, set

public void setOwners(OwnerRepository r){

this.owners = r;

}

등으로 주입할 수 있다.

 

주의점 : final로 생성하면 무조건 생성하거나 생성자를 만들어야 한다.

 

 

생성자를 생성하자 않고 @Autowried로 생성자 주입도 가능하다. (자주 사용하는 방법)

bean으로 등록되어있지않으면 의존성주입은 불가능하다.

'개발 소발 > 개발 Spring' 카테고리의 다른 글

Spring Autowired와 ComponentScan이란  (0) 2020.07.10
AOP란? AOP 기초개념  (0) 2020.07.09
프레임워크 개념  (0) 2018.01.26
SpringMVC란?  (0) 2018.01.11
MVC패턴이란?  (0) 2018.01.11

다형성은 편리하고 유용하지만
모든 경우에 사용할 수 없다.

출력연산자가 대표적인 경우이다.

출력연산자를 사용하려면 함수를 따로 하나 생성하여 오버라이딩 한다.
즉, 함수에게 기능을 위임한다.

비슷한 전략이 다른경우에도 사용될 수 있다.

 

핵심. 새로운 가상함수를 이용해 문제점을 해결한다.

#include <iostream>

using namespace std;

class Base
{
public:
	Base() {};

	friend ostream& operator << (ostream &out, const Base &b)
	{
		return b.print(out);
	}

	virtual ostream& print(ostream& out) const
	{
		out << "Base";
		return out;
	}

};

class Derived : public Base
{
public:
	Derived() { };

	virtual ostream& print(ostream& out)const override
	{
		out << "Derived";
		return out;
	}
};
int main() {

	Base b;
	cout << b << endl;

	Derived d;
	cout << d << endl;


	return 0;
}

자식 클래스의 포인터를 부모클래스의 포인터로 바꾼후 다시 자식클래스포인터로 변경하기.

auto와 dynamic_cast를 사용한다.

dynamic_cast는 casting에 실패하면 nullptr을 반환한다.

static_cast도 사용가능하다. static_cast는 할 수 있는한 최대한 변환시킨다.
안되야 될것도 되는 경우가 생긴다. 하지만 사용할 만한 가치는 있다.
static_cast는 nullptr을 반환하지 않는다.

 

#include <iostream>
#include <string>

using namespace std;

class Base
{
public:
	int m_i = 0;
	virtual void print()
	{
		cout << "I'm Base" << endl;
	}

};

class Derived1 : public Base
{
public:
	int m_d = 0;
	virtual void print() override
	{
		cout << "I'm Derived1" << endl;
	}

};

class Derived2 : public Base
{
public:
	int m_d = 0;
	string m_s = "123";
	virtual void print() override
	{
		cout << "I'm Derived2" << endl;
	}

};
int main()
{

	Derived2 d2;
	Base *b = &d2;
	b->print();

	auto *base_to_d2 = dynamic_cast<Derived2*>(b);

	cout << base_to_d2->m_s << endl;


	return 0;
}

상속할때 보통 부모클래스보다는 자식클래스가 정보를 많이 가지고 있다.
이유 : 추가 함수,추가 변수,오버라이딩 등등

작은 부모의 객체에 큰 자식클래스를 대입한다면 다형성이 사라진다.

부모객체로 인식한다고 생각하면된다.

 

즉, 자식에 추가된 데이터들이 사라진다.


reference_wapper를 사용하거 포인터를 사용하면 객체잘림을 막을 수 있다.

 

당연, 포인터를 생각해보면 주소값을 대입하는거니 데이터는 그대로 남아있겠지?

 

#include <iostream>
#include <vector>

using namespace std;

class Base
{
public:
	int m_i = 0;
	virtual void print() 
	{
		cout << "I'm Base" << endl;
	}

};

class Der : public Base
{
public:
	int m_d = 0;
	virtual void print() override 
	{
		cout << "I'm Der" << endl;
	}

};
int main()
{
	Der d;
	Base b = d;
	b.print();
	
	//reference_wrapper를 사용하거나 *포인터 사용이가능하다.
	vector<reference_wrapper<Base>> my_vec;
	my_vec.push_back(b);
	my_vec.push_back(d);

	//reference_wrapper는 ref를 받아오기위해 get()을 사용한다.
	for (auto & a : my_vec)
		a.get().print();

	vector<Base*> my_vec2;
	my_vec2.push_back(&b);
	my_vec2.push_back(&d);

	for (auto & a : my_vec2)
		a->print();
	return 0;
}

다이아몬드 상속문제.

해결하기위해 가상 기본 클래스를 사용한다.
숫자는 class :는 상속을 나타낸다.
아래처럼 상속하면 다이아몬드 구조로 될거 같지만
1class가 두번 생성된다.
1
2 :(상속) 1 , 3 :(상속) 1
4 :(상속) 2,3

상속할때 상속하는 코드에 virtual을 쓰면 한번만 생성된다.
맨 마지막 자식 테이블에서 1class 생성자를 정의해준다.

 

#include <iostream>

using namespace std;
class PowerDevice
{

public:
	int m_power;
	PowerDevice(int power){
		cout << "PowerDevice : " << power << endl;
	}

};

class Scanner : virtual public PowerDevice
{
public:
	Scanner(int scan, int power)
		:PowerDevice(power) {
		cout << "Scanner : " << scan << endl;
	}
};

class Printer : virtual public PowerDevice
{
public:
	Printer(int prin, int power)
		:PowerDevice(power) {
		cout << "Printer : " << prin << endl;
	}
};

class Copier : public Scanner, public Printer
{
public:
	Copier(int scan, int prin, int power)
		:Scanner(scan, power), Printer(prin, power) ,PowerDevice(power){}
};
int main()
{
	Copier c(1, 2, 3);
	//주소값이 다르다.
	cout << &c.Scanner::PowerDevice::m_power << endl;
	cout << &c.Printer::PowerDevice::m_power << endl;
	return 0;
}

다형성을 설계관점을 만든다.

기본클래스에서 자식클래스에게 제약을 걸어준다.

순수 가상 함수
- 자식클래스에서 반드시 오버라이딩 해주어야한다.
- virtual 로 선언되고 마지막이 = 0; 이면 순수 가상 함수
- 별도로 함수를 구현할 수 있다. 호출할 수 없다.

virtual void speak() const = 0;



추상 기본클래스
- 순수 가상함수가 포함이된 클래스
- abstact class는 인스턴스생성이 안된다.

class Animal
{
protected:
	string m_name;

public:
	Animal(string name)
		:m_name(name) {}

public:
	string getName() { return m_name; }

	virtual void speak() const = 0;
};


인터페이스
- 순수 가상 함수로만 이루어진 클래스
- 파라미터를 인터페이스로 받으면 다양하게 
구현된 자식 클래스들을 이용할 수 있다.

class IErrorlog
{
public:
	virtual bool reportError(const char * errorMessage) = 0;

	virtual ~IErrorlog() {}
};


순수 가상 함수가 있는 Class를 상속받으면 무조건 오버라이딩해야한다.

 

#include <iostream>
#include <string>

using namespace std;

class Animal
{
protected:
	string m_name;

public:
	Animal(string name)
		:m_name(name) {}

public:
	string getName() { return m_name; }

	virtual void speak() const = 0;
};

//void Animal::speak() const
//{
//	cout << m_name << " ??? " << endl;
//}

class Cat :public Animal
{
public:
	Cat(string name)
		:Animal(name) {

	}

	void speak() const
	{
		cout << m_name << " Meow " << endl;
	}
};


class Dog :public Animal
{
public:
	Dog(string name)
		:Animal(name) {

	}

	void speak() const
	{
		cout << m_name << " Woof " << endl;
	}
};

class Cow : public Animal
{
public:
	Cow(string name)
		:Animal(name) {

	}

	//void speak() const
	//{
	//	cout << m_name << " Woof " << endl;
	//}

};


//Interface 예제
//자신이 정의하지 않고 순수가상함수만 가지고 있다.
//Interface는 대문자 I를 붙여주는게 관습이다.
class IErrorlog
{
public:
	virtual bool reportError(const char * errorMessage) = 0;

	virtual ~IErrorlog() {}
};

class FileErrorLog : public IErrorlog
{
public:
	virtual bool reportError(const char * errorMessage)
	{
		cout << "File Error" << endl;
		return true;
	}

};

//부모클래스를 이용하기에 여러방법이 사용가능하다.
void logTest(IErrorlog & log) {
	log.reportError("Runtime Error!!");
}

int main()
{
	//speak()가 구현이 안되어있다.
	//Cow c("n");

	Cat c("cat");
	c.speak();

	FileErrorLog log;
	logTest(log);

	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 객체잘림,reference wapper  (0) 2019.09.30
c++ 다이아몬드 상속문제 해결  (0) 2019.09.30
c++ vitual,가상함수  (0) 2019.09.30
c++ 정적,동적바인딩  (0) 2019.09.30
c++ 가상소멸자  (0) 2019.09.23

virtual 함수 (가상함수)는 정적이 아닌 동적으로 실행된다.

함수를 실행시 virtual펑션 테이블(포인터)을 찾고 테이블안에 함수 포인터를 사용한다.


다형성 사용시 자식 클래스에도 virtual펑션 테이블(포인터)가 생긴다.
동적바인딩 될때 오버라이딩된 함수는 자식 테이블 함수 주소 값을 가진다.

virtual을 사용하면 sizeof()로 확인시  포인터 메모리까지 할당되는 걸 볼 수 있다.

 

#include <iostream>

using namespace std;

class Base
{
public:
	virtual void fun1() {};
	void fun2() {};
};

class Der : Base
{
public:
	void fun1() {};
	void fun3() {};

};

int main()
{
	cout << sizeof(Base) << endl;
	cout << sizeof(Der) << endl;
	return 0;
}

가상함수의 내부동작 이해

속도면에선 static,dynamic중에 static이 빠르다.

정적 바인딩은 프로그램이 실행될때 메모리에 배치된다.
동적 바인딩은 그때 그때 배치된다.

 

dynamic바인딩이란? 

쉽게말해 함수 포인터로 동적할당해 그때그때 맞게 함수 주소를 대입한다.


dynamic은 포인터로 주소를 타고 가야한다.
하지만 dynamic 즉 동적바인딩을 쓰면 프로그래밍이 유연해진다.

 

#include <iostream>

using namespace std;

int add(int x, int y) {
	return x + y;
}

int subtract(int x, int y) {
	return x - y;
}

int multiply(int x, int y) {
	return x * y;
}
int main()
{
	int x, y;
	cin >> x >> y;

	int op;
	cout << "0 : add, 1 : subtract, 2 : multiply" << endl;

	cin >> op;
	// statuc binding (early binding)
	/*int result;
	switch (op) 
	{
	case 0: result = add(x, y); break;
	case 1: result = subtract(x, y); break;
	case 2: result = multiply(x, y); break;
	}
	cout << result << endl;
	*/

	//Dynamic binding (late binding)
	int(*func_ptr)(int, int) = nullptr;
	switch (op) {
		case 0: func_ptr = add; break;
		case 1: func_ptr = subtract; break;
		case 2: func_ptr = multiply; break;
	}
	cout << func_ptr(x, y) << endl;
	return 0;
}

가상 소멸자

상속구조에서 생성자는 자식클래스는 자식클래스 생성자를 사용한다.
하지만 소멸자는 가상 소멸자를 사용하는게 좋다.

소멸자를 생성하지 않으면 자식소멸자,부모소멸자 순으로 실행된다.

하지만 부모클래스에 자식클래스를 대입한후 부모클래스를 delete하면 자식클래스의 소멸자가 실행안돼
메모리 릭이 생긴다.

부모클래스 소멸자에 virtual 을 해주면 자식 클래스의 소멸자도 실행시켜준다.

 

#include <iostream>

using namespace std;

class Base
{
public:
	virtual ~Base() {
		cout << "~Base()" << endl;
	}

};

class Derived : public Base
{
private:
	int *m_array;

public:
	Derived(int len)
	{
		m_array = new int[len];
	}

	~Derived() {
		cout << "~Derived" << endl;
		delete[] m_array;
	}

};
int main()
{
	Derived *d = new Derived(5);
	Base *b = d;

	delete b;

	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ vitual,가상함수  (0) 2019.09.30
c++ 정적,동적바인딩  (0) 2019.09.30
c++ override,final,공변 반환값  (0) 2019.09.23
c++ 가상함수와 다형성  (0) 2019.09.23
c++ 다형성의 기본개념  (0) 2019.09.23

override,final,공변 반환값

공변 반환형
Covariant

다형성쓸때 유용하게 쓸 수 있다.

오버로딩(파라미터가 다름)을 하면 상속이 적용안된다.

메소드명 뒤 override 사용하기!

오버라이드 할려했는데 실수로 오버로딩할 수 있다.

그걸 체크하려면 메소드 명 뒤에 override를 붙여준다.

class A {
virtual void printOverride(int x)  const{ cout << "A" << endl; }
}

class B : public A{
void printOverride(int x)  const override { cout << "B" << endl; }
}



메소드명 뒤 final 사용하기!

하위 클래스가 override하지 않게 하고싶을 수 있다.

상위 클래스 메소드명 뒤에 final을 붙여주면 오버라이드 할 수 없다.

virtual void printFinal(int x) final { cout << "A" << endl; }




반환값이 다르면 오버라이딩 안되지만

반환값이 부모자식관계면 오버라이딩이 된다.

오버라이딩은 되지만 부모 인스턴스에 자식을 대입하면 부모클래스가 출력된다.

class A{
	virtual A *getThis() { return this; }
};
class B : public A{
	B *getThis() { return this; }
};

 

코드 내용

#include <iostream>

using namespace std;

class A
{
public:
	virtual void printOverride(int x)  const{ cout << "A" << endl; }
	
	//final을 붙이면 오버라이드 할 수 없다.
	virtual void printFinal(int x) final { cout << "A" << endl; }
	
	void print() { cout << "A" << endl; }
	virtual A *getThis() { return this; }
};

class B : public A
{
public:
	void printOverride(int x)  const override { cout << "B" << endl; }
	
	//에러난다.
	//virtual void printFinal(int x) { cout << "B " << endl; }
	
	void print() { cout << "B" << endl; }
	B *getThis() { return this; }
};
int main() {

	A a;
	B b;
	
	A &ref = b;
	b.getThis()->print();
	ref.getThis()->print();

	cout << typeid(b.getThis()).name() << endl;
	//부모클래스가 출력된다.
	cout << typeid(ref.getThis()).name() << endl;
	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 정적,동적바인딩  (0) 2019.09.30
c++ 가상소멸자  (0) 2019.09.23
c++ 가상함수와 다형성  (0) 2019.09.23
c++ 다형성의 기본개념  (0) 2019.09.23
c++ 상속받은 함수를 오버라이딩하기  (0) 2019.08.30

+ Recent posts