Eu começaria não pensando em um gerente de ativos . Pensar na sua arquitetura em termos vagamente definidos (como "gerente") tende a permitir que você varra mentalmente muitos detalhes para baixo do tapete e, consequentemente, fica mais difícil escolher uma solução.
Concentre-se nas suas necessidades específicas, que parecem estar relacionadas à criação de um mecanismo de carregamento de recursos que abstrai o armazenamento de origem subjacente e permite extensibilidade do conjunto de tipos suportado. Não há realmente nada na sua pergunta a respeito, por exemplo, do armazenamento em cache de recursos já carregados - o que é bom, porque, de acordo com o princípio de responsabilidade única, você provavelmente deve criar um cache de ativos como uma entidade separada e agregar as duas interfaces em outros lugares. , como apropriado.
Para abordar sua preocupação específica, você deve projetar seu carregador para que ele não faça o carregamento de nenhum ativo, mas delegue essa responsabilidade a interfaces personalizadas para carregar tipos específicos de ativos. Por exemplo:
interface ITypeLoader {
object Load (Stream assetStream);
}
Você pode criar novas classes que implementam essa interface, com cada nova classe sendo adaptada para carregar um tipo específico de dados de um fluxo. Ao usar um fluxo, o carregador de tipos pode ser gravado em uma interface comum e independente de armazenamento e não precisa ser codificado para carregar do disco ou de um banco de dados; isso permitiria que você carregasse seus ativos a partir de fluxos de rede (o que pode ser muito útil na implementação de recarga a quente de ativos quando o jogo estiver sendo executado em um console e suas ferramentas de edição em um PC conectado à rede).
Seu principal carregador de ativos precisa ser capaz de registrar e rastrear esses carregadores específicos de tipo:
class AssetLoader {
public void RegisterType (string key, ITypeLoader loader) {
loaders[key] = loader;
}
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
A "chave" usada aqui pode ser o que você quiser - e não precisa ser uma string, mas é fácil começar com isso. A chave levará em consideração a forma como você espera que um usuário identifique um ativo específico e será usada para procurar o carregador apropriado. Como você deseja ocultar o fato de que a implementação pode estar usando um sistema de arquivos ou um banco de dados, não é possível ter usuários que se referem a ativos por um caminho do sistema de arquivos ou algo assim.
Os usuários devem se referir a um ativo com um mínimo de informações. Em alguns casos, apenas um nome de arquivo por si só seria suficiente, mas descobri que muitas vezes é desejável usar um par de tipo / nome para que tudo seja muito explícito. Assim, um usuário pode se referir a uma instância nomeada de um dos seus arquivos XML de animação como "AnimationXml","PlayerWalkCycle".
Aqui, AnimationXmlseria a chave com a qual você se registrou AnimationXmlLoader, o que implementa IAssetLoader. Obviamente, PlayerWalkCycleidentifica o ativo específico. Dado um nome de tipo e um nome de recurso, o carregador de ativos pode consultar seu armazenamento persistente em busca dos bytes brutos desse ativo. Como estamos buscando a máxima generalidade aqui, você pode implementar isso fornecendo ao carregador um meio de acesso ao armazenamento ao criá-lo, permitindo substituir o meio de armazenamento por qualquer coisa que possa fornecer um fluxo posteriormente:
interface IAssetStreamProvider {
Stream GetStream (string type, string name);
}
class AssetLoader {
public AssetLoader (IAssetStreamProvider streamProvider) {
provider = streamProvider;
}
object LoadAsset (string type, string name) {
var loader = loaders[type];
var stream = provider.GetStream(type, name);
return loader.Load(stream);
}
public void RegisterType (string type, ITypeLoader loader) {
loaders[type] = loader;
}
IAssetStreamProvider provider;
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
Um provedor de fluxo muito simples simplesmente procuraria em um diretório raiz de ativos especificado um subdiretório nomeado typee carregaria os bytes brutos do arquivo nomeado nameem um fluxo e o retornaria.
Em resumo, o que você tem aqui é um sistema em que:
- Há uma classe que sabe ler bytes não processados de algum tipo de armazenamento de back-end (um disco, um banco de dados, um fluxo de rede, qualquer que seja).
- Existem classes que sabem como transformar um fluxo de bytes brutos em um tipo específico de recurso e devolvê-lo.
- O seu "carregador de ativos" real apenas possui uma coleção dos itens acima e sabe como canalizar a saída do provedor de fluxo para o carregador específico do tipo e, assim, produzir um ativo concreto. Ao expor maneiras de configurar o provedor de fluxo e os carregadores específicos do tipo, você tem um sistema que pode ser estendido pelos clientes (ou por você) sem precisar modificar o código do carregador de ativos real.
Algumas advertências e notas finais:
O código acima é basicamente C #, mas deve ser traduzido para praticamente qualquer idioma com o mínimo de esforço. Para facilitar isso, omiti muitas coisas, como verificação de erros ou uso adequado IDisposablee outros idiomas que podem não se aplicar diretamente em outros idiomas. Esses são deixados como lição de casa para o leitor.
Da mesma forma, retorno o ativo concreto como objectacima, mas você pode usar genéricos ou modelos ou qualquer outra coisa para produzir um tipo de objeto mais específico, se quiser (você deve, é bom trabalhar com ele).
Como acima, eu não lido com cache aqui. No entanto, você pode adicionar o armazenamento em cache facilmente e com o mesmo tipo de generalidade e configurabilidade. Experimente e veja!
Há muitas, muitas e muitas maneiras de fazer isso, e certamente não há uma maneira ou consenso, e é por isso que você não conseguiu encontrar uma. Tentei fornecer código suficiente para transmitir os pontos específicos sem transformar essa resposta em uma parede de código dolorosamente longa. Já é extremamente longo como é. Se você tiver perguntas esclarecedoras, sinta-se à vontade para comentar ou me encontrar no chat .