F #의 애플리케이션 아키텍처 / 구성
나는 최근에 C #에서 SOLID를 매우 극단적 인 수준으로 해왔고 어느 시점에서 내가 본질적으로 요즘 함수를 구성하는 것 외에는 많은 일을하지 않는다는 것을 깨달았습니다. 최근에 F #을 다시 살펴보기 시작한 후, 지금하고있는 대부분의 작업에 대해 훨씬 더 적절한 언어 선택이 될 것이라고 생각했기 때문에 실제 C # 프로젝트를 F #으로 이식 해보고 싶습니다. 개념 증명으로. 나는 실제 코드를 (매우 비 관상적인 방식으로) 뽑아 낼 수 있다고 생각하지만, C #에서와 비슷하게 유연한 방식으로 작업 할 수있는 아키텍처가 어떻게 생겼는지 상상할 수 없습니다.
내가 의미하는 것은 IoC 컨테이너를 사용하여 구성하는 작은 클래스와 인터페이스가 많고 Decorator 및 Composite와 같은 패턴도 많이 사용한다는 것입니다. 그 결과 (제 생각에는) 애플리케이션의 어느 지점에서나 기능을 쉽게 교체하거나 확장 할 수있는 매우 유연하고 발전 가능한 전체 아키텍처가 생깁니다. 필요한 변경의 크기에 따라 새로운 인터페이스 구현을 작성하고 IoC 등록에서 교체하고 완료하면됩니다. 변경 사항이 더 커도 객체 그래프의 일부를 교체 할 수 있으며 나머지 응용 프로그램은 이전과 동일하게 유지됩니다.
이제 F #을 사용하면 클래스와 인터페이스가 없습니다 (할 수 있다는 것을 알고 있지만 실제 함수 프로그래밍을 수행하려는 시점을 벗어난 것 같습니다), 생성자 주입도없고 IoC도 없습니다. 용기. 나는 고차 함수를 사용하여 Decorator 패턴과 같은 것을 할 수 있다는 것을 알고 있지만, 생성자 주입을 사용하는 클래스와 같은 종류의 유연성과 유지 관리 성을 제공하지 않는 것 같습니다.
다음 C # 유형을 고려하십시오.
public class Dings
{
public string Lol { get; set; }
public string Rofl { get; set; }
}
public interface IGetStuff
{
IEnumerable<Dings> For(Guid id);
}
public class AsdFilteringGetStuff : IGetStuff
{
private readonly IGetStuff _innerGetStuff;
public AsdFilteringGetStuff(IGetStuff innerGetStuff)
{
this._innerGetStuff = innerGetStuff;
}
public IEnumerable<Dings> For(Guid id)
{
return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
}
}
public class GeneratingGetStuff : IGetStuff
{
public IEnumerable<Dings> For(Guid id)
{
IEnumerable<Dings> dingse;
// somehow knows how to create correct dingse for the ID
return dingse;
}
}
내가 해결하기 위해 내 IoC 컨테이너를 말씀 드리죠 AsdFilteringGetStuff
위해 IGetStuff
와 GeneratingGetStuff
그 인터페이스를 자신의 의존성을 위해. 이제 다른 필터가 필요하거나 필터를 모두 제거하면 각각의 구현이 필요할 수 있으며 IGetStuff
간단히 IoC 등록을 변경합니다. 인터페이스가 동일하게 유지 되는 한 응용 프로그램 내 에서 항목 을 만질 필요가 없습니다 . DIP로 활성화 된 OCP 및 LSP.
이제 F #에서 무엇을합니까?
type Dings (lol, rofl) =
member x.Lol = lol
member x.Rofl = rofl
let GenerateDingse id =
// create list
let AsdFilteredDingse id =
GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")
나는 이것이 얼마나 적은 코드인지를 좋아하지만 유연성을 잃습니다. 예, 유형이 동일하기 때문에 AsdFilteredDingse
또는 GenerateDingse
같은 장소에서 전화를 걸 수 있습니다. 하지만 호출 사이트에서 하드 코딩하지 않고 어떤 전화를 걸지 어떻게 결정합니까? 또한이 두 기능은 서로 바꿔서 사용할 수 있지만 이제이 기능 AsdFilteredDingse
을 변경하지 않고는 내부 생성기 기능을 교체 할 수 없습니다 . 이것은별로 좋지 않습니다.
다음 시도 :
let GenerateDingse id =
// create list
let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
generator id |> List.filter (fun x -> x.Lol = "asd")
이제 AsdFilteredDingse를 더 높은 차수의 함수로 만들어 구성 가능성이 있지만 두 함수는 더 이상 상호 교환 할 수 없습니다. 다시 생각해 보면 아마 그렇게해서는 안됩니다.
What else could I do? I could mimic the "composition root" concept from my C# SOLID in the last file of the F# project. Most files are just collections of functions, then I have some kind of "registry", which replaces the IoC container, and finally there is one function that I call to actually run the application and that uses functions from the "registry". In the "registry", I know I need a function of type (Guid -> Dings list), which I'll call GetDingseForId
. This is the one I call, never the individual functions defined earlier.
For the decorator, the definition would be
let GetDingseForId id = AsdFilteredDingse GenerateDingse
To remove the filter, I'd change that to
let GetDingseForId id = GenerateDingse
The downside(?) of this is that all functions that use other functions would sensibly have to be higher order functions, and my "registry" would have to map all functions that I use, because the actual functions defined earlier can't call any functions defined later, in particular not those from the "registry". I might also run into circular dependency issues with the "registry" mappings.
Does any of this make sense? How do you really build an F# application to be maintainable and evolvable (not to mention testable)?
This is easy once you realize that Object-Oriented Constructor Injection corresponds very closely to Functional Partial Function Application.
First, I'd write Dings
as a record type:
type Dings = { Lol : string; Rofl : string }
In F#, the IGetStuff
interface can be reduced to a single function with the signature
Guid -> seq<Dings>
A client using this function would take it as a parameter:
let Client getStuff =
getStuff(Guid("055E7FF1-2919-4246-876E-1DA71980BE9C")) |> Seq.toList
The signature for the Client
function is:
(Guid -> #seq<'b>) -> 'b list
As you can see, it takes a function of the target signature as input, and returns a list.
Generator
The generator function is easy to write:
let GenerateDingse id =
seq {
yield { Lol = "Ha!"; Rofl = "Ha ha ha!" }
yield { Lol = "Ho!"; Rofl = "Ho ho ho!" }
yield { Lol = "asd"; Rofl = "ASD" } }
The GenerateDingse
function has this signature:
'a -> seq<Dings>
This is actually more generic than Guid -> seq<Dings>
, but that's not a problem. If you only want to compose the Client
with GenerateDingse
, you could simply use it like this:
let result = Client GenerateDingse
Which would return all three Ding
values from GenerateDingse
.
Decorator
The original Decorator is a little bit more difficult, but not much. In general, instead of adding the Decorated (inner) type as a constructor argument, you just add it as a parameter value to a function:
let AdsFilteredDingse id s = s |> Seq.filter (fun d -> d.Lol = "asd")
This function has this signature:
'a -> seq<Dings> -> seq<Dings>
That's not quite what we want, but it's easy to compose it with GenerateDingse
:
let composed id = GenerateDingse id |> AdsFilteredDingse id
The composed
function has the signature
'a -> seq<Dings>
Just what we're looking for!
You can now use Client
with composed
like this:
let result = Client composed
which will return only [{Lol = "asd"; Rofl = "ASD";}]
.
You don't have to define the composed
function first; you can also compose it on the spot:
let result = Client (fun id -> GenerateDingse id |> AdsFilteredDingse id)
This also returns [{Lol = "asd"; Rofl = "ASD";}]
.
Alternative Decorator
The previous example works well, but doesn't really Decorate a similar function. Here's an alternative:
let AdsFilteredDingse id f = f id |> Seq.filter (fun d -> d.Lol = "asd")
This function has the signature:
'a -> ('a -> #seq<Dings>) -> seq<Dings>
As you can see, the f
argument is another function with the same signature, so it more closely resembles the Decorator pattern. You can compose it like this:
let composed id = GenerateDingse |> AdsFilteredDingse id
Again, you can use Client
with composed
like this:
let result = Client composed
or inline like this:
let result = Client (fun id -> GenerateDingse |> AdsFilteredDingse id)
For more examples and principles for composing entire applications with F#, see my on-line course on Functional architecture with F#.
For more about Object-Oriented Principles and how they map to Functional Programming, see my blog post on the SOLID principles and how they apply to FP.
참고URL : https://stackoverflow.com/questions/22263779/application-architecture-composition-in-f
'Development Tip' 카테고리의 다른 글
PostgreSQL 프로세스가 "트랜잭션 유휴"상태라는 것은 무엇을 의미합니까? (0) | 2020.10.27 |
---|---|
Java에서 표준 Trie 기반 맵 구현은 어디에서 찾을 수 있습니까? (0) | 2020.10.27 |
Android 개발을 위해 Vim을 어떻게 설정할 수 있습니까? (0) | 2020.10.27 |
MySQL 데이터베이스를 SQLite 데이터베이스로 내보내기 (0) | 2020.10.27 |
RequireJS : 템플릿 및 CSS를 포함한 모듈로드 (0) | 2020.10.27 |