Attualmente sto scrivendo un'applicazione server Minecraft (personalizzata) in C #. Ho pensato che fosse un buon modo per insegnarmi un sacco di cose importanti come la concorrenza e soprattutto l'efficienza della memoria (dovuta semplicemente alla vastità dello spazio oggetti in Minecraft).
Mentre ero in treno ieri, stavo facendo fatica a "pensare" veramente a un modo per organizzare il modulo di rete. Volevo una semplice interfaccia esterna che fornisse i necessari (lascia che gli utenti reagiscano a una connessione ricevuta, al messaggio ricevuto e quando una connessione viene interrotta). Tuttavia, volevo anche che fosse abbastanza astratto in modo tale da consentire a entrambi i client di gioco reali di connettersi o semplici client falsi.
Ho iniziato a sovrastimolare le cose a la IConnection
, INetworkModule
, IConnectionFactory
, IConnectionProtocol
, IMessageEncoder
, IMessageDecoder
e così via. Regno di nomi in abbondanza. Non solo, ma non stavo davvero ottenendo ovunque - mi sentivo come se stessi ponendo il problema fondamentale dietro altri livelli di astrazione in quanto non avevo idea di come volevo organizzare la comunicazione tra tutti questi moduli .
Poi sono arrivato al pensiero - perché non uso solo verbs (azioni / funzioni / metodi / qualsiasi cosa) invece di nomi (tipi) - concentriamoci su quello che voglio ottenere per ora, e dividerlo in nomi più tardi!
E così, mi è venuto in mente qualcosa di simile a questo:
private static Func<IDisposable> Listen(
Action<TcpClient> onConnectionReceived,
Action<TcpClient> onConnectionDropped,
Action<TcpClient, int, byte[]> onBytesReceived,
Func<bool> isOpen)
{
// todo: 25565 needs to be injected
// todo: give opportunity to supply listener
// todo: refactor listener into role interface
var listener = new TcpListener(IPAddress.Any, 25565);
return () => Observable.FromAsync(listener.AcceptTcpClientAsync)
.DoWhile(isOpen)
.Subscribe(client =>
{
var stream = client.GetStream();
// on subscribe invoke the delegate first
onConnectionReceived(client);
// the delegate has the option to disconnect the client
// based on whatever criteria it see fit.
// we should now create an observable to
// listen for messages on this client.
Observable.Defer(() =>
{
// 8kb buffer for each connection
// this is actually fairly small
var buffer = new byte[8024];
// we can handle the 'doing' of things with these bytes
// inside the passed function. This function will be doing too much otherwise
return Observable.FromAsync(ct => stream.ReadAsync(buffer, 0, buffer.Length, ct))
.Select(n => new {Read = n, Bytes = buffer});
}).Subscribe(a =>
{
// drop connection if nothing was read
if (a.Read <= 0)
onConnectionDropped(client);
else
onBytesReceived(client, a.Read, a.Bytes);
});
});
}
Davvero semplice. Beh, voglio dire, non proprio, ma questo fondamentalmente fa il ruolo di tutti i nomi (eccetto IMessageEncoder/Decoder
che ho elencato prima), ma solo facendo ipotesi sul fatto che abbiamo bisogno di un TcpClient
(ovviamente, che isn ' Quello che voglio adesso, ma posso lavorarci in iterazione 2!)
Tuttavia, il mio problema è che questo non è realmente il codice C # tipico e sicuramente interrompe l'SRP in quanto questa funzione restituisce una funzione per l'intera esecuzione di un server.
Ma - ha senso per me.
Quindi la mia domanda è: esiste un reale svantaggio intrinseco nell'usare un paradigma funzionale-esista come questo al contrario del tradizionale Kingdom of Nouns OOP all'interno di C # - tradizionalmente un linguaggio multi-paradigma? E se ci sono aspetti negativi, cosa dovrei affrontare e perché?
Per la cronaca, intendo che questo sia l'OSS, quindi c'è un altro problema in quanto alcuni sviluppatori potrebbero non capire lo stile del codice perché semplicemente non è orientato all'OC, solo funzioni pure.