Reputation: 1056
To move from one http client to another (same concept can be applied to log libs, db's) I implemented this solution but I am not sure if it's a pattern in OOP world. After looking at some it kinda looks like strategy pattern or repository pattern. But what I understood from repository pattern it is only restricted to data access functionality. However same concept can be applied logging libs, http clients or email client etc.
Here is my implementation of something that I don't know the name: ( =D )
const fetch = require('node-fetch');
const axios = require('axios');
class MyHttpAxios {
constructor(axios) {
this.axios = axios;
}
async get(url) {
const response = await this.axios.get(url);
return response.data;
}
}
class MyHttpFetch {
constructor(fetch) {
this.fetch = fetch;
}
async get(url) {
const response = await this.fetch(url);
return response.json();
}
}
const httpClients = {
axios: new MyHttpAxios(axios),
fetch: new MyHttpFetch(fetch),
};
const client = httpClients['axios'];
client
.get('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response)
.then(json => console.log(json));
Upvotes: 0
Views: 368
Reputation: 148
Before the answer I would like to say that, the idea in Strategy pattern and its derivations is to take the advantage of dynamic dispatch
and in this way instead of manually managing conditional logic to make behavior parameterization in the code, leaving the selection of polymorphic/conditional behavior to the runtime. IMO strategy pattern is the most basic and practical form of polymorphism. Dynamic-typed languages does it with duck typing but the idea is same.
You are using some kind of strategy pattern here by taking the advantage of duck typing which I needed to add this also, because there is a typescript
tag in the question (Actually typescript and class keyword added with ES6 doesn't change the nature of javascript which is a language using protoypal inheritance, but If we think in terms of GoF with this syntactical sugar I needed to add this).
I see a few issues here. First one is whether is it worth the effort or not because you could directly use axios.get(url).then(...)
or fetch(url).then(...)
which I mean there is no really a polymorphic behavior in this case, what change or the conditional behaviour here is just the http client and minor method(httpClient.get/x/y/z().then() to httpClient.url().then()
) and from this perspective if your code would only keep single method also look like a command pattern
which implies the usage of just lambda functions. However if your purpose is to create a library and if your concrete classes will be a wrapper above http clients' multiple methods and then provide users a selection then it would be finer. So your concretes will be wrappers.
Some of the GoF methods are actually wrappers and what distinguish them is their intent
s and actually a Facade
would be enough here.
With classical inheritance and static typing, the idea to use Strategy/Policy pattern is could be below:
The client/driver class is:
class Client {
fetchPolicy = PolicyFactory.getFetchPolicy('axios');
data = new Fetcher().fetchSomeData(fetchPolicy, 'https://jsonplaceholder.typicode.com/todos/1');
}
A basic factory class to choose concrete implementation:
class PolicyFactory {
const httpClients = {
axios: new MyHttpAxios(),
fetch: new MyHttpFetch(),
};
public static getFetchPolicy(policyType: string): FetchPolicy {
return Object.keys(httpClients).find(key => key === policyType);
}
}
Which is actually unnecessary and just using httpClients['axios'] with httpClients object/map
does above.
Parameterized behaviour:
interface FetchPolicy {
get(url: string): any;
}
class MyHttpAxios implements FetchPolicy {
async get(url) {
const response = await this.axiosGet(url);
return response.data;
}
private axiosGet(url) {
axios // Since it is global use directly, or if you don't pass as a parameter in the client
.get(url)
.then(response => response)
.then(json => console.log(json));
}
}
class MyHttpFetch implements FetchPolicy {
async get(url) {
const response = await this.fetch(url).then(/*...*/);
return response.json();
}
}
Which also roughly similar to your code.
While said this you could just use lambdas to provide this kind of abstraction:
const fetch = require('node-fetch');
const axios = require('axios');
((url, httpClientFn, transformFn) => {
return httpClientFn(url).then(response => transformFn(response))
})("http://...", url => axios.get(url), response => console.log(response));
//somewhere in other clients which use the fetch
((url, httpClientFn, transformFn) => {
return httpClientFn(url).then(response => transformFn(response))
})("http://...", url => fetch.url(url), response => console.log(response));
Upvotes: 1
Reputation: 819
I think that is more an array of Classes than a true Strategy Pattern. Should that, what you have created, be considered a pattern? I doubt it, since OOP design patters are mainly based on inheritance to reduce code/design complexity.
Your solution needs one more step to be considered a pattern. Becasue, at this point your classes MyHttpAxios
and MyHttpFetch
have no relationship between them.
What is a Strategy Pattern structure for your case? Based on the "oficial" structure of the design pattern, your design should look something like:
--------------- -----------
| SomeClass | <>----- | MyHttp<I> |
| client:MyHttp | | |
--------------- | +get() |
-----------
^ ^
/ \
/ \
--------------- ---------------
| FetchStrategy | | AxiosStrategy |
| +get() | | +get() |
--------------- ---------------
So, SomeClass
is where you will instantiate your Strategy. MyHttp
is an Interface (not planed to have instances). FetchStrategy
and AxiosStrategy
are your clases where you implement your code (very similar to your code).
Upvotes: 0