이름 지어주면 들어가도록
게임매니저에서 싱글톤형성
current scene을 생성하도록 수정
아이템 리스트들도 생성하도록 수정
where T : Scene // 제네릭, 씬을 상속받는 T만 허용
---------
정적클래스(static class) 는 인스턴스 생성자체가 불가능하기때문에
일반적인 생성자는 정의 할수없다.
단 static 생성자는 선언가능한데,
static GameManager()
{
}
형태로 선언하고 (public없음)
해당 타입이
처음으로 참조되기직전에 스코프 내 코드블럭이 한번만 실행된다.
일반적으로 클래스에서 statement (실행문)은 작성할수없으니
그대로 맴버로 객체인스턴스를 생성하는 구문을 쓰기는 어렵다.
생성자, 메서드등에서 객체인스턴스를 생성할수있다.
-------------
라이브강의 -반복문
for문에
int만 쓰는것은아님
조건식에 char도 사용가능
foeach문은 인덱스를 사용하지않음
컬렉션 내에서 꺼내쓰는데 그 컬렉션의 타입을 컬렉션 내부의 타입으로 받아준다.
string[] 이면 string
List<int> 면 int로 받는다.
foreach (var item in 컬렉션)
{
}
foreach는 읽기전용이다.
foreach(int n in numbers)
{
n = n+1; // 컴파일 오류: foreach 내부에서는 직접수정불가
}
실습--
--------
콘솔 TextRpg에 쓰기전
델리게이트 좀더 공부
델리게이트 : 메서드를 참조하는 타입
메서드를 가리키는 형식화된 포인터라 생각하면된다.
구현: 사용하고자하는 메서드의 형식대로 써주되 , 이름은 사용할 변수명, 사용자정의명으로만든다.
실제로 특정메서드에 접근이 불편할 수 있음. 이래서 델리게이트를 씀
런타임에 "이화면이 보여질때" 또는 "입력처리후" 어떤 로직을 실행할지 간단히 바꿀수있음
콜백, 입네트패턴:
Ui 컴포넌트끼리 결합도를 낮추고, 어떤 이벤트 발생시점에서 여러구독자가 알림을
받도록할수있음.
가장 좋은 예시가 버튼류. unity에서도 버튼은 이런식의 원리대로 움직임
public class Button
{
public Action OnClick;
public void Click() => OnClick?.Invoke();
}
선언예시
public delegate string MyDelegate(int x);
//매개변수 하나 int로 받고 , 반환값은 string이다
string Foo(intx) { ...}
MyDelegate d = Foo;
string result = d(123); // 일반호출
string result2 = d.Invoke(123) // Invoke()로도 호출가능
그런데,
델리게이트와 상속/인터페이스 를 쓰는 타이밍이 아직 초보라서 헷갈린다.
좀만더 파보자면,
-------
화면들이 공통기능이 많고 타입안정성이 필요할때는 = 추상클래스 또는 인터페이스
런타임에 "특정시점에 실행할 로직"을 유연하게 교체해야할때는 = 델리게이트 또는 이벤트
서비스 호출, 후처리 같은 부가로직을 분리할때는 = 델리게이트 ("이후처리" 콜백)
화면 전환 자체를 전략패턴으로 관리하고싶을때 = 팩토리 + 인터페이스 결합
------
이렇게 구분해서 사용한다. 잘은모르지만 혼합사용이 좋아보인다.
모든 화면에서 공용으로 쓸 메서드들은 클래스를 추상클래스 혹은 인터페이스화해서 정의만하고
다른데서도 상속받으면 좋다.
다만, 내부에서 입력처리 후 콜백한다거나 구패,판매같은 사후로직, 외부서비스 호출결과
반영등을 델리게이트(Action/Func)을 활용하면 좋을것같다.
복습으로 Action, Func의 경우 미리 제너릭 형식으로 정의된 델리게이트로
반환값, 매개변수들을 사용자정의 델리게이트에서 미리 구현을 해줘야한다면
Func은 반환값이 있는 메서드
Action은 반환값이 없는 메서드로
Func 은 반환값도 제너릭 T 타입을 다 쓸수있다.
Func<int, string> int 매개변수를 받고 string을 반환한다
Action<int,string>이라 하면 둘다 매개변수다, 반환값이 없다.
static int Add(int x, int y)
{
return x+y;
}
staid void PrintMessage(string message)
{
Console.writeLine(message)
}
이런식으로 되어있다면
Main 함수 (실행창) 안에서
Func<int, int, int>addFunc = Add;
int result = addFunc(3,5);
Console.WritelIne($"결과 {result}")
Action<string>printAction = PrintMessage;
printAction("Hello,World!");
이런식으로 쓸수있다.
< > 안에는 자료형과 같은 매개변수, 혹은 out 할 타입이 들어간다.
매개변수란에 일반메서드는안되나 델리게이트형식의 메서드 참조는 값으로 들어가진다.
public void ProcessNumber(Action<int> callback) // 소괄호안에 변수는 델리게이트 형식 메서드참조값
{
callback(42);
}
인터페이스와 추상클래스의 차이를 좀 보자
인터페이스는 클래스가아니므로 다중상속가능, 모든 멤버를 상속받는 클래스가 구현해야함
멤버들을 정의만한다.
클래스에 대한 제약조건을 명시하는것이다. 계약이라고한다. 객체 생성불가
굳이 자식클래스에서 오버라이딩 키워드를 지정하지않게 막는다.
프로퍼티, 메소드, 인덱서, 이벤트만을 가진다.
인터페이스는 이를 구현한 클래스의 부모라 할수있어
인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있다.
ILogger logger = new ConsoleLogger(); 같은식이다.
매개변수가 인터페이스 타입이면
메서드 호출시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야한다는 말이다.
public void Method1(ILogger logger)
{
// 어쩌고
}
반환타입이 엔터페이스 타입이라는것은
메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환해야된다는 의미다.
public ILogger Method1()
{
ConsoleLogger c = new ConsoleLogger();
return c;
}
추상클래스는 다중상속이 불가능하다, 마찬가지로 객체생성불가능하다
그리고 안에 일반 메서드, 추상메서드 둘다 존재가능하다
버츄얼 메서드도 들어갈수있다
추상메서드 추상프로퍼티는 statc,virtual 키워드를 쓸수없다
실제 구현을 제공하지는 않기에 메서드 본문이없고 정의만한다.
추상메서드는 자식클래스에서 오버라이딩해서 쓴다
----------------------
델리게이트로
다시 돌아가서...
Action<T> , Func<T, TResult> 형태의 각종 예제를 머릿속에넣으며 공부해보자
Action<int> printDouble = x => Console.WriteLine( x*2);
printDouble(5); // 10
printDouble.Invoke(7); // 14
Action<T1,T2>, Action<T1,T2,T3> .... 최대 16인자까지 제공됌.
Action(non-generic)도 존재하는데
즉 매개변수없이 void 반환하는것이다.
// 매개변수 없이 void 반환
Action noArgs = () => Console.WriteLine("파라미터 없이 실행");
noArgs(); // OK
Func<TResult> 과 Func<T,TResult>
반환값이 있고, 매개변수도 있을 수 있는 형태
Func<TResult> : 매개변수가 없고 반환만한다.
Func<int>getRandom = ()=> new Random().Next(1,101);
int num = getRandom(); // 1~100 사이 랜덤 int 난수 호출
int num2 = getRandom.Invoke(); // 비슷한 메커니즘
Func<T,TResult>
Func<string, int> parse = s=> int.Parse(s);
int x = parse("42");
최대 16개인자제공
람다식
람다표현식은 델리게이트 인스턴스를 만들때 가장많이 쓰는 문법임
(매개변수리스트) => {메서드 본문;}
// 간단할때는 중괄호와 return 생략가능
x => x*x // 매개변수 x, 반환 x*x
(a, b) => a+b // 두개의 매개변수 a, b 반환 a+b
( ) => DateTime.Now // 매개변수없음, 반환 현재시간
예시
Action sayHello = ( ) => Console.WriteLine("Hello") // ()는 매개변수없음, 즉 Action sayHello 는 아무변수도안받고 해당 실행문을 호출하는 익명메서드다.
sayHello(); // Hello
Func<int, int, int> add = (a, b) => a+b;
int sum = add(3,5); // 8
델리게이트 호출방식
직접호출
d(123);
add(3,5);
sayHello();
.Invoke() 메서드로 호출
d.Invoke(123);
add.Invoke(3,5);
sayHello.Invoke();
둘은 기능상 완전히 동일하다.
.Invoke()는 가독성 측면에서 "델리게이트 호출"임을 명시적으로 드러내지만
보통은 d(args) 형태를 더 많이쓴다.
단, 델리게이트(또는 이벤트)는 내부에 여러메서드를 연결한(invoke list)구조가 될수있다.
d += A; d+=B; 처럼 구독 두번하면
d.Invoke(arg);
이 한번의 호출로 A(arg) -> B(arg) 순차 실행이 일어난다.
일반 델리게이트도, 이벤트도 모두 동일하다.
널 체크후 호출해야한다
델리게이트가 null(아직 아무 메서드도 안붙였을때)이면 호출시 예외가 난다.
if(d != null) d(123);
d?.Invoke(123);
람다식 ( x=> ...) 은 데릴게이트(메서드 참조객체)를 간편하게 만드는 익명메서드방식의 문법이다.
바로 좀더 응용해봐서
else if (_player.Inventory.InventoryItems.Exists(x => x.Id == Shoplist[value-1].Id))
{
Console.WriteLine("이미 구매한 아이템입니다.");
}
이구문에서 쓰인 Exists 안에 람다식은 델리게이트와 아주 큰 연관이있는구문이다.
해당 eles if 조건문 안의 내용은 text rpg 중에 만든 내용인데
플레이어 인벤토리아이템이라는 리스트가 있고 그안에서 Exists 실행문으로 체크하였을때
내부 람다식을 bool 타입으로 반환한다.
그래서 조건식 안에 조건체크에 쓰일수있는것인데,
풀어쓰자면
Shoplist라는 리스트안에 있는 인덱스에 해당하는 아이템의 Id가
매개변수로 넣은 아이템의 Id와 일치하냐를 확인하여 참이면
실행문 "이미 구매한 아이템입니다" 를 내뿜는다.
Exists의 시그니처와 델리게이트를 보자면
public class List<T>
{
public bool Exists(Predicate<T> match); // Exists는 Predicate<T>라는 델리게이트를 인자로 받는다.
}
Predicate<T>는 아래와같이 정의된 델리게이트 타입이다.
public delegate bool Predicate<T>(T obj);
Predicate<T> 는 "T 타입을 받아서 bool을 반환하는 메서드 서명(델리게이트)이라는것이다.
x => x.Id == Shoplist[value-1].Id
는
Predicate<Item> match = delegate(Item x) { return x.Id == Shoplist[value-1].Id;
};
와 같다.
LINQ 메서드의 Func<T,boll>도 동일하다.
LINQ의 Where,Any,FirstOrDefault 등은 Func<T,bool> 또는 Func<T,TResult> 같은 델리게이트 타입을 받는다.
using System.Linq;
bool has = InventoryItems.Any(x => x.Id == someId);
컬렉션 탐색, 필터링메서드들은 델리게이트 타입의 파라미터를 요구하기때문에 해당구문처럼 쓸수있는것이다.
람다식을 넘기면 컴파일러가 알아서 해당 델리게이트 인스턴스로 변환해 주는 것이다.
델리게이트에 메서드를 할당하면 인스턴스가 생성된다고 보면된다
해당 인스턴스는 참조형 값으로 소괄호에 매개변수 인자로도 쓰일수있어진다.
-------------
팩토리 패턴에 대해서 공부해보자
팩토리 패턴은 객체 생성 책임을 한곳에 모아서,
클라이언트(호출자)는 "어떤 객체를 만들지" 에만 집중하고,
"객체를 어떻게(언제, 어떤 매개변수로) 만드는지" 는 팩토리에 맡기는 디자인 패턴이다.
팩토리 패턴 왜씀? 예시로 Screen만드는걸로 해보자
new SomeScreen (...) 을 여기저기 흩어놓지않고,
팩토리 메서드(혹은 델리게이트)에만 모아두면 관리가 편해진다.
새로운 화면(Screen)을 추가할때마다 팩토리 등록만 해주면되니 유연성, 확장성에서도 좋다
기존 코드를 직접 수정할 필요없이 KeyofScreen enum과 팩토리맵만 업데이트하면된다.
타입적으로도
Func<Screen> 시그니처를 강제하면
팩토리에 등록된 람다는 무조건 Screen을 반환해야하고 잘못된타입을 실수로 반환할일은없다.
Func<Screen> 자체가 전역설정을 완전히 없애주진않음
하지만 객체생성책임을 한곳에 모으고 필요할때만 호출하게 해줌으로써 전역 의존도를
크게 줄여준다.
'Unity 개발 공부' 카테고리의 다른 글
| [내배캠] 본캠 12일차.스테이트 머신, 스테이트패턴, FSM, 제너릭 T, 디자인패턴종류 (0) | 2025.04.22 |
|---|---|
| [내배캠] 본캠 11일차.전역설정,싱글톤,의존성주입, 용어정리 (0) | 2025.04.21 |
| [내배캠] 본캠 9일차. 메서드 연산자 복습 , 그리고 TextRpg 진행과정 (5) | 2025.04.17 |
| [내배캠] 본캠 8일차. 인터페이스, 열거형, 예외처리, 참조형, 값형, 델리게이트, 람다, LINQ (0) | 2025.04.16 |
| [내배캠] 본캠 7일차. 기초문법 조건문,반복문, 메서드, 재귀함수, 클래스, 복습 (0) | 2025.04.15 |