Design de classe: permita que uma classe seja usada como objeto e também forneça métodos estáticos públicos
-
21-09-2019 - |
Pergunta
Eu tenho uma classe boba e pequena "FileSystemSize"Que pode ser usado como objeto e também por métodos estáticos públicos. A saída é semelhante, mas não idêntica em cada caso.
A aula era intimamente estático, mas adicionei a possibilidade de inicializá -lo como um objeto para permitir a extensão com o novo "Métodos de conveniência"Em versões futuras, sem a necessidade de muita análise de parâmetros. Por exemplo, eu tenho Getkbstring (), GetMbstring (), etc ... Métodos para permitir que o tamanho do arquivo formatasse convenientemente da maneira que eu quero (como uma string). Internamente, a classe armazena o tamanho do byte de arquivo como um dobro.
Estou um pouco confuso se isso faz sentido. Parece que eu talvez deva dividir isso em uma versão estática e uma versão de objeto como a Microsoft faz para o Directory e o DirectoryInfo. No entanto, parece mais fácil para mim ter tudo isso em um só lugar com um nome que não pode ser enganado - deve ficar claro o que o Sistema de arquivos faz? Existem implicações para a manutenção que eu não estou antecipando? Que cheiro é esse?
var mypath = @"C:\mypath";
var filesystemsize = new FileSystemSize(mypath);
string kilobytes = filesystemsize.GetKBString();
string megabytes = filesystemsize.GetMBString();
double bytes = filesystemsize.ByteSize;
double staticbytes = FileSystemSize.GetDirectoryBytesSize(new DirectoryInfo(mypath));
double statickilobytes = FileSystemSize.ConvertSize(staticbytes, "KB");
Solução
Veja de outra maneira: por que você está colocando métodos de formatação de string/número/UI no seu método de Sistema de arquivos?
Embora possa ser usado em relação aos arquivos, essa é uma peça geral de funcionalidade que o IMHO deve ser encontrado em outras partes de uma biblioteca bem organizada - assim como as funções do caminho não fazem parte do arquivo ou das classes de diretório em .NET, eu colocaria o Métodos "formato de um número" em uma classe String ou Maths Utils.
Separe as responsabilidades de seus objetos e você pode descobrir que não há necessidade de misturar membros estáticos e não estáticos em casos como este.
Outras dicas
Um bom teste: se você está se perguntando a si e a nós se está tudo bem, há uma chance de que não seja.
Pode não ser natural que os usuários da classe tenham alguns métodos acessíveis via classe e outros via objeto, especialmente quando o segundo não exige realmente as propriedades da instância da classe. Provavelmente, eles ficarão confusos e estarão se referindo a: "WTF Este programador fez isso?!".
Sugiro seguir todos os métodos de instância, se você gosta da possibilidade de estender a classe, com métodos de extensão ou via subclasse.
Como você não está tendo muito estado no FileSystemSize, esse não é o candidato perfeito para métodos de extensão?
Eu pessoalmente forneceria extensões para formatar números como seqüências de tamanho de arquivo e usar um enum
Para especificar como formatar os tamanhos dos arquivos:
public static class FileSystemSize
{
public static long GetDirectoryBytesSize(string path);
}
public static class NumberExtensions
{
public static string FormatAsFileSize(
this long fileSize, FileSizeStringFormat format);
}
public enum FileSizeStringFormat
{
KiloByte,
MegaByte,
}
Se você estiver usando o C# 3.0, suas intenções poderão ser melhor expressas com métodos de extensão e IFORMATPROVERS. No código, esse pode ser um método de extensão nos métodos FileInfo e DirectoryInfo ToString, para que eles lessem algo assim:
var directorySize = myDirectory.ToString("GB");
var fileSize = myFile.ToString("MB");
O código acima parece mais natural para o que você está tentando fazer.
Veja como o seguinte funciona para você. Algumas delas precisarão ser testadas (o método recursivo DirectoryInfoextender.GetDirectorySize vem à mente). Se você precisar escrever declarações como Console.WriteLine("{0:GB}", fileInfo)
, você também pode considerar escrever um IformatProvider também.
Observe também que eu intencionalmente economizei em verificações nulas e manuseio de exceções para esses métodos publicamente acessíveis.
public static class DirectoryInfoExtender
{
public static string ToString(this DirectoryInfo d, string format, int fractionalDigits)
{
double fileSize = GetDirectorySize(d);
return FileSizeConverter.GetFileSizeString(fileSize, format, fractionalDigits);
}
public static double GetDirectorySize(DirectoryInfo d)
{
var files = d.GetFiles();
var directories = d.GetDirectories();
if(files.Length == 0 && directories.Length == 0)
{
return 0;
}
else
{
double size = 0;
foreach(var file in files)
{
size += file.Length;
}
foreach(var directory in directories)
{
size += GetDirectorySize(directory);
}
}
return size;
}
}
public static class FileInfoExtender
{
public static string ToString(this FileInfo f, string format, int fractionalDigits)
{
return FileSizeConverter.GetFileSizeString(f.Length, format, fractionalDigits);
}
}
public class FileSizeConverter
{
public static string GetFileSizeString(double fileSize, string format, int fractionalDigits)
{
long divisor;
string sizeIndicator;
switch(format.ToLower().Trim())
{
case "gb":
divisor = (long)Math.Pow(2, 30);
sizeIndicator = "gigabytes";
break;
case "mb":
divisor = (long) Math.Pow(2, 20);
sizeIndicator = "megabytes";
break;
case "kb":
divisor = (long)Math.Pow(2, 10);
sizeIndicator = "kilobytes";
break;
default:
divisor = 1;
sizeIndicator = "bytes";
break;
}
return String.Format("{0:N" + fractionalDigits +"} {1}", fileSize / divisor, sizeIndicator);
}
}
O cheiro padrão é o uso de métodos estáticos - isso dificulta a manutenção, caso você use esses métodos em todo o seu código.
Outro cheiro IMHO é: o nome da classe não está claro sobre o que realmente faz. A partir da sua descrição, é destinado a formatar dados, caso em que eu o mencionaria no nome da classe.