这个API可以改进吗?

时间:2009-08-24 04:11:41

标签: c# api refactoring

在我们的CORE库中,我们将此类提供为20,000行抽象。 你能看出它的设计方式有什么问题吗?

注1:此类具有SharpZipLib支持。

注2:SharpZipLib约为20K线。

public static class Compression
{
    public static Byte[] CompressBytes(Byte[] input);
    public static Byte[] CompressBytes(Byte[] input, Format format);
    public static Byte[] CompressBytes(Byte[] input, Format format, Level level);

    public static Byte[] DecompressBytes(Byte[] input);
    public static Byte[] DecompressBytes(Byte[] input, Format format);

    public static String CompressString(String input);
    public static String CompressString(String input, Format format);
    public static String CompressString(String input, Format format, Level level);

    public static String DecompressString(String input);
    public static String DecompressString(String input, Format format);

    public static void CompressFile(String input_file_path, String output_file_path);
    public static void CompressFile(String input_file_path, String output_file_path, Format format);
    public static void CompressFile(String input_file_path, String output_file_path, Format format, Level level);

    public static void DecompressFile(String input_file_path, String output_file_path);
    public static void DecompressFile(String input_file_path, String output_file_path, Format format);

    public static void CompressFolder(String input_folder_path, String output_file_path);
    public static void CompressFolder(String input_folder_path, String output_file_path, Format format);
    public static void CompressFolder(String input_folder_path, String output_file_path, Format format, Level level);

    public static void DecompressFolder(String input_file_path, String output_file_path);
    public static void DecompressFolder(String input_file_path, String output_file_path, Format format);
}

8 个答案:

答案 0 :(得分:11)

我建议将这个单独的类分成几个类。一般来说,静态实用程序类会破坏很多规则,其中最重要的是分离关注点。虽然是,但是这个类中的所有方法都处理压缩,它们关注的是压缩不同的东西。一些压缩字节数组,一些压缩字符串,一些压缩文件。我会将这个单独的实用程序分解为多个实用程序:

public static class ByteCompression
{
    public static Byte[] Compress(Byte[] input);
    public static Byte[] Compress(Byte[] input, Format format);
    public static Byte[] Compress(Byte[] input, Format format, Level level);

    public static Byte[] Decompress(Byte[] input);
    public static Byte[] Decompress(Byte[] input, Format format);
}

public static class StringCompression

    public static String Compress(String input);
    public static String Compress(String input, Format format);
    public static String Compress(String input, Format format, Level level);

    public static String Decompress(String input);
    public static String Decompress(String input, Format format);
}

public static class FileCompression
{
    public static void Compress(String input_file_path, String output_file_path);
    public static void Compress(String input_file_path, String output_file_path, Format format);
    public static void Compress(String input_file_path, String output_file_path, Format format, Level level);

    public static void Decompress(String input_file_path, String output_file_path);
    public static void Decompress(String input_file_path, String output_file_path, Format format);
}

public static FolderCompression
{
    public static void Compress(String input_folder_path, String output_file_path);
    public static void Compress(String input_folder_path, String output_file_path, Format format);
    public static void Compress(String input_folder_path, String output_file_path, Format format, Level level);

    public static void Decompress(String input_file_path, String output_file_path);
    public static void Decompress(String input_file_path, String output_file_path, Format format);
}

上述实用程序类减少了重复,更好地封装目的,与其成员方法更具凝聚力,并且意图更清晰。您确实有四种静态实用程序类型而不是一种,但您并没有以这种方式破坏尽可能多的规则/最佳实践。尽量避免单片,do-everything实用程序类。如果可以的话,找到一种方法来使它们成为实例类而不是静态类,特别是如果在每个压缩/解压缩方法中使用的类级别存在任何共享数据。这将提高线程安全性。

编辑:

正如andy评论的那样,更理想的实现将使用扩展方法。文件和文件夹压缩作为扩展实现起来有点困难,但我已经尝试过了。以下示例更好地实现了我的目标:将名词(或主语)与动词(或操作)分离,提供更清晰的API,最终减少重复,保持关注点分离,并进行适当的封装。

public static class ByteCompressionExtensions
{
    public static byte[] Compress(this byte[] input);
    public static byte[] Compress(this byte[] input, Format format);
    public static byte[] Compress(this byte[] input, Format format, Level level);

    public static byte[] Decompress(this byte[] input);
    public static byte[] Decompress(this byte[] input, Format format);
}

// In use:
byte[] myArray = new byte[] { ... };
byte[] compArray = myArray.Compress();
// Subject (noun) -----^      ^----- Operation (verb)


public static class StringCompressionExtensions
{
    public static byte[] Compress(this string input);
    public static byte[] Compress(this string input, Format format);
    public static byte[] Compress(this string input, Format format, Level level);

    // Extension method fail!! :( :( This conflicts with Decompress from the class above!
    public static string Decompress(this byte[] input);
    public static string Decompress(this byte[] input, Format format);
}

// In use:
string myStr = "A string!";
byte[] compArray = myStr.Compress();
// Subject (noun) ---^      ^----- Operation (verb)
myStr = compArray.Decompress(); // Fail! :(


public static class FileCompressionExtensions
{
    public static void Compress(this FileInfo input, FileInfo output);
    public static void Compress(this FileInfo input, FileInfo output, Format format);
    public static void Compress(this FileInfo input, FileInfo output, Format format, Level level);

    public static void Decompress(this FileInfo input, FileInfo output);
    public static void Decompress(this FileInfo input, FileInfo output, Format format);
}

// In use:
FileInfo myFile = new FileInfo(input_file_path);
FileInfo myCompFile = new FileInfo(output_file_path);
                 myFile.Compress(myCompFile);
// Subject (noun) --^      ^----- Operation (verb)
                 myCompFile.Decompress(myFile);


public static class FolderCompressionExtensions
{
    public static void Compress(this DirectoryInfo input, DirectoryInfo output);
    public static void Compress(this DirectoryInfo input, DirectoryInfo output, Format format);
    public static void Compress(this DirectoryInfo input, DirectoryInfo output, Format format, Level level);

    public static void Decompress(this DirectoryInfo input, DirectoryInfo output);
    public static void Decompress(this DirectoryInfo input, DirectoryInfo output, Format format);
}

// In use:
DirectoryInfo myDir = new DirectoryInfo(input_folder_path);
DirectoryInfo myCompDir = new DirectoryInfo(output_folder_path);
                 myDir.Compress(myCompDir);
// Subject (noun) --^      ^----- Operation (verb)
                 myCompDir.Decompress(myDir);

答案 1 :(得分:5)

VS2010有一个明显的改进,你可以选择参数。

另一件可能有用的事情是提供扩展方法,以便我可以这样做: input_folder_path.CompressFolder(output_file_path).DecompressFolder(OUTPUTFILE);

这将允许我压缩,然后解压缩压缩的内容以验证压缩。

如果我想压缩文件夹并将其放在与输入文件路径相同的级别,为什么我必须指定输出文件呢?

因此,如果我执行CompressFolder(@“C:\ input_folder”)并保留它,那么它将使用C:作为输出路径。

答案 2 :(得分:3)

首先,我建议您查看Casey Muratori的精彩演讲:http://www.mollyrocket.com/873
(不幸的是,你必须单独关注幻灯片和音频)

如果您打算保留一个单一的课程,我个人的偏好是:

public static Byte[] CompressGzip(Byte[] input);
public static Byte[] CompressGzip(Byte[] input, Level level);

public static Byte[] DecompressGzip(Byte[] input);

public static String CompressGzip(String input);
public static String CompressGzip(String input, Level level);

etc

即。 知道它们是字节,编译器知道它们是字节,为什么我必须输入?但是,保持Gzip的正面和中心非常重要,因为要求用Gzip压缩的数据用相同的方法解压缩。当然,如果您可以将字节数组编码为字符串或其任何组合,则不起作用。

IE中。否则这段代码看起来很可疑:

Format f = Format.NotDefault;

// Use our non-standard compression
String compressed = Compress("my name", f);

// more code, or transfer across the network

// Uh oh! Decompression failed.
// The default parameters are broken in this case!
String decompressed = Decompress(compressed);

通过将方法放在名称中,可以确保每个人都考虑压缩字节的格式。

此外,您可以为不同的引擎添加额外的压缩选项 - 例如LZMA的字典大小参数。

答案 3 :(得分:2)

另一个不在.net 4中的改进可能是创建一个具有String input_folder_path, String output_file_path, Format format, Level level属性的类CompressInfo,并且只有一个方法可以检查属性是否为null。

答案 4 :(得分:2)

我可能会进行一次重构改进:

public sealed class CompressOptions
{
  public Format Format { get; set; }
  public Level Level { get; set; }
}

然后,每个压缩目标可以减少到2个方法。使用Byte []压缩器作为示例。

public static Byte[] Compress(Byte[] input)
{
    Compress(input, new CompressOptions { Format=Zip, Level=Normal });
}
public static Byte[] Compress(Byte[] input, CompressOptions options)
{
    if( options == null )
        throw new ArgumentNullException("options");

    // compress-away
}

然后,调用者代码可以使用他们想要的任何选项,而无需为每个可能的场景提供覆盖,这似乎是每个场景(字节,字符串,文件)重复一次。

Byte[] b = GetSomeData();
var result = Compress(b, new CompressOptions { Format=Gzip } );
var result2 = Compress(b, new CompressOptions { Level=Store } );
var result3 = Compress(b);

您可能也希望CompressOptions也是不可变的(即一旦设置,就无法更改值)

此设计还允许将压缩选项传递到需要压缩某些内容的代码中,而无需知道要使用的压缩。

对于可能需要更多选项的其他压缩器,您可以从CompressOptions中创建子类(尽管首先解封它,并密封任何叶类)。这里有很多变化。

答案 5 :(得分:1)

继jrista之后,我会继承它们,因为我可以假设它有一些共同的功能:

abstract class CompressorBase<T> { }

然后考虑使用表格的标准方法:

public CompressionResult Compress (T toCompress, CompressionParams paramaters)
{
}

然后,至少,课程本身正在做出明确的决定,只需根据“CompressionParams”课程的变化做出决定。

这很不错,因为你不再需要改变公共API,只需对该类进行更改,然后'Compressor'就能完成其余的工作。

答案 6 :(得分:-2)

对于压缩方法,请考虑返回压缩等信息。类似地,对于解压缩,状态指示是否由于某种原因任何文件/文件夹没有解压缩。

在压缩/解压缩的情况下,通常有效的情况是该方法不成功。例如,尝试压缩正在使用的文件或解压缩位置可能会覆盖某些内容。因为这些不是“异常”,所以在这种情况下不应抛出异常,并且建议将返回值作为返回值或“out”参数返回。

答案 7 :(得分:-2)

  

在我们的CORE库中,我们将此类作为20,000行抽象提供....

20k线?真的吗?这不是API表面,而是你的问题。我的意思是,CompressString(string)只是在调用CompressString(string, Format, Level) - 对吧?肯定CompressString(string, Format, Level)基本上包括:

byte[] b = System.Text.Encoding.Default.GetBytes(input);
byte[] c = CompressBytes(b, format, level);
return Convert.ToBase64(c);

全部是3行 - 带有临时变量。我可以想到其余部分的类似实现。

所以 - 这让我相信CompressBytes(byte[], Format, Level)必须大约19,500行。我会说那是你的问题。