一个逐步“优化”的范例程序(转)

reference URL:
http://www.tracefact.net/Software-Design/A-Sample-Design.aspx

本文是《Object-Oriented Analysis and Design》一书第一章和第五章的读书笔记。我对书中提供的一个范例程序进行了总结和整理,通过逐步优化这个乐器管理的范例程序,分析了进行程序设计时需要注意到的一些问题。

1.简单直接的实现

这个程序起初的需求很简单:我们需要创建一个吉他管理程序,它能够保存所有的吉他信息,并且可以通过输入吉他的参数来进行查询,返回查询结果。我们知道一个优良的软件应该从两个角度去衡量:

  • 从用户的角度,软件应该是符合用户期望的,也就是满足了用户的需求,可以完成用户期望它完成的工作。
  • 从开发者的角度,软件应该是易于维护的、可扩展的,以及可重用的。

这两个方面应该是递进的,也就是说软件首先要能满足用户的需求。所以我们先看如何完成用户的需求,我们定义一个类Guitar,它代表了吉他;以及一个Inventory类,它用于维护现有吉他的信息,可以进行添加、查找等操作:

然后我们来看下实现:

// 吉他类
public class Guitar {
    private string serialNumber;   // 序列号
    private string builder;        // 厂商
    private string model;          // 型号
    private string type;           // 类型
    private string backWood;       // 后部材质
    private string topWood;        // 前面材质
    private double price;          // 价格

    public Guitar(string serialNumber, string builder, string model, string type, string backWood, string topWood, double price) {
        this.backWood = backWood;
        this.builder = builder;
        this.model = model;
        this.price = price;
        this.serialNumber = serialNumber;
        this.topWood = topWood;
        this.type = type;
    }
   
    // 公有属性省略
}

public class Inventory {
    // 维护现有的所有吉他
    private List<Guitar> guitarList;
    public Inventory() {
        guitarList = new List<Guitar>();
    }

    // 向列表中添加 吉他
    public void AddGuitar(string serialNumber, string builder, string model, string type, string backWood, string topWood, double price) {
        Guitar guitar = new Guitar(serialNumber, builder, model, type,backWood, topWood, price);

        guitarList.Add(guitar);
    }

    // 搜索吉他列表,寻找满足searchGuitar参数的吉他
    // 如果searchGuitar的参数为null或者"",则忽略此参数
    public Guitar Search(Guitar searchGuitar) {
        List<Guitar>.Enumerator it =  guitarList.GetEnumerator();
        // 价格Price和序列号SerialNumber不参与查询
        Guitar result = null;
        while (it.MoveNext()) {
            Guitar guitar = it.Current;
            string builder = searchGuitar.Builder;
            if(!String.IsNullOrEmpty(builder) &&
                !builder.Equals(guitar.Builder))
                continue;

            string model = searchGuitar.Model;
            if(!String.IsNullOrEmpty(model) &&
                !model.Equals(guitar.Model))
                continue;

            string type = searchGuitar.Type;
            if (!String.IsNullOrEmpty(type) &&
                !type.Equals(guitar.Type))
                continue;

            string backWood = searchGuitar.BackWood;
            if (!String.IsNullOrEmpty(backWood) &&
                !backWood.Equals(guitar.BackWood))
                continue;

            string topWood = searchGuitar.TopWood;
            if (!String.IsNullOrEmpty(topWood) &&
                !topWood.Equals(guitar.TopWood))
                continue;

            result = guitar;   // 找到第一个匹配结果就返回
            return result;
        }

        return result;
    }
}

接下来我们向Inventory中添加一些Guitar,然后来测试下查找功能:

class Program {
    static void Main(string[] args) {

        Inventory inventory = new Inventory();
        initializeInventory(inventory);

        // 想要查找的Guitar
        Guitar wanted = new Guitar("", "fender", "Stratocastor", "electric", "Alder", "Alder", 0);

        // 返回符合条件的结果
        Guitar guitar = inventory.Search(wanted);

        if (guitar != null) { // 找到符合条件的结果
            Console.WriteLine("You might like this {0} {1} {2} guitar:\n {3} back and sides,\n {4} top.\n You can have it for only ${5} !", guitar.Builder, guitar.Model, guitar.Type, guitar.BackWood, guitar.TopWood, guitar.Price);
        } else {
            Console.WriteLine("Sorry, nothing found.");
        }
    }

    private static void initializeInventory(Inventory item) {
        item.AddGuitar("V95693", "Fender", "Stratocastor", "electric", "Alder", "Alder", 1499.95D);
       item.AddGuitar("B95315", "Gibson", "SpecialKind", "electric", "Maple", "Cedar", 2134.30D);
        item.AddGuitar("V95694", "Fender", "Stratocastor", "electric", "Alder", "Alder", 1599.95D);
    }
}

结果我们发现并未返回搜索结果,但是我们看下InitializeInventory()方法,确实存在一个Guitar,它的属性完全符合要查找的Guitar实例wanted。为什么查询却找不到呢?仔细查看一下,我们发现添加到Inventory中的Guitar的制造商Builder是"Fender",而输入的searchGuitar的Builder属性为"fender"。我们知道,在C#中字符串的大小写是敏感的,即是说 "a"=="A"返回的是false,所以"Fender"不等于"fender"。所以我们遇到的问题是:在不要求严格匹配大小写的情况下,对于字符串的比较,我们应该先全部转换为大写或者小写,然后再进行比较。但是这样做就没有问题了么?我们看一下Guitar类的定义,除了price为double类型以外,其余均为string。而某些属性,比如说吉他的发声类型type,只有两种可能,一种是传统的、通过震动发声的(Acoustic),一种是电子发声的(Electric);对于制造商Builder,可能只有有限的几个厂家。但使用string类型时,我们无法对于这些属性的取值进行限制,此时,我们应该考虑:如果对象的属性是由有限个项目构成的集合,我们最好定义一个枚举,并设置对象的属性为这个枚举类型。

所以对于上面程序可以进行的第一个改进,就是定义枚举,并将部分属性的值,由string改为枚举类型:

// 发生类型
public enum SoundType {
    Acoustic, Electric
}
// 制造商
public enum Builder {
    Fender, Martin, Gibson, Collings, Olson
}
// 木料
public enum Wood {
    IndianRoseWood, BrazilianRoseWood, Mahogany, Maple, Cocobolo, Cedar, Alder, Sitka
}

同时修改Guitar类和Inventory类,让它们使用这些枚举作为字段类型:

public class Guitar {
    private string serialNumber;   // 序列号
    private Builder builder;       // 厂商
    private string model;          // 型号
    private SoundType type;        // 类型
    private Wood backWood;         // 后部材质
    private Wood topWood;          // 前面材质
    private double price;          // 价格
   
    // 构造函数、属性做相应修改,此处略
}

此时,我们发现上面例子Inventory中符合搜索条件的有两项,而Search()方法只能返回查询到的第一个结果,所以第二处改进就是对Inventory的Search()方法进行修改,让它返回一个查询结果列表:

public class Inventory {  
    private List<Guitar> guitarList; // 维护现有的所有吉他

    public Inventory() {
        guitarList = new List<Guitar>();
    }

    // AddGuitar()方法略...

    // 搜索吉他列表,寻找满足searchGuitar参数的吉他
    public List<Guitar> Search(Guitar searchGuitar) {
        List<Guitar>.Enumerator it = guitarList.GetEnumerator();

        List<Guitar> list = new List<Guitar>();    // 保存满足搜索条件的吉他
        while (it.MoveNext()) {
            Guitar guitar = it.Current;

            if (guitar.Builder!=searchGuitar.Builder)
                continue;
            string model = searchGuitar.Model.ToLower();
            if (!String.IsNullOrEmpty(model) &&
                !model.Equals(guitar.Model.ToLower()))
                continue;
            if (guitar.Type != searchGuitar.Type)
                continue;                         
            if (guitar.BackWood != searchGuitar.BackWood)
                continue;
            if (guitar.TopWood != searchGuitar.TopWood)
                continue;

            list.Add(guitar);  // 添加到列表中
        }

        return list;    // 返回结果
    }
}

然后我们进行一下测试,可以看到它返回了两个结果。

static void Main(string[] args) {         
    Inventory inventory = new Inventory();
    initializeInventory(inventory);

    // 想要查找的Guitar
    Guitar wanted = new Guitar("", Builder.Fender, "Stratocastor", SoundType.Electric, Wood.Alder, Wood.Alder, 0);
               
    // 返回符合条件的结果
    List<Guitar> list = inventory.Search(wanted);

    if (list.Count > 0) {
        foreach (Guitar guitar in list) {
            Console.WriteLine("You might like this {0} {1} {2} guitar:\n {3} back and sides,\n {4} top.\n You can have it for only ${5} !", guitar.Builder, guitar.Model, guitar.Type, guitar.BackWood, guitar.TopWood, guitar.Price);
        }
    } else {
        Console.WriteLine("Sorry, not found.");
    }
}

这里仍然需要注意一个问题:上面我们将Guitar的字段类型由string改为了枚举,虽然我们限制了输入,字段只能接受有限的数值,但是我们在调用Search()方法时,必须明确的指定一个枚举值。而有时候,我们并不希望指明数值(我们希望忽略此查询条件),比如说,我们不希望限制吉他的木料(任何木料的吉他都满足查询条件),在使用string类型时,我们只需要传递null或者空字符串("")进去就可以了,但使用枚举后却必须指定一个数值。此时,可以向枚举中添加一个字段,NotSet,这个值相当于string为null或空字符串("")时的情况。然后将Search()方法中的判断语句进行一下修改就可以了:

if (searchGuitar.TopWood != Wood.NotSet &&
    guitar.TopWood != searchGuitar.TopWood)
    continue;

2.属性分离和解耦

属性分离

我们再对上面的程序稍微进行一下分析,发现对于Guitar来说,SerialNumber和Price属性是一定会有的,而其他的属性以后可能会添加,比如说我们可能会再添加一个NumStrings属性,代表吉他有多少根玄;也可能会删除某个属性,比如我们可能以后会觉得model属性多余,然后把它删除掉。除此以外,我们发现Inventory类的Search()方法只需要Guitar的部分属性,而我们传递了整个Guitar进去。

此时,我们可以将不变的部分(SerialNumber和Price)仍保留在Guitar类中,将可能会变化的部分(Guitar类的其他属性),封装为另一个类型,我们称为GuitarSpec,并在Guitar中保存一个GuitarSpec类型实例:

public class GuitarSpec {
    private Builder builder;       // 厂商
    private string model;          // 型号
    private SoundType type;        // 类型
    private Wood backWood;         // 后部材质
    private Wood topWood;          // 前面材质

    public GuitarSpec(Builder builder, string model, SoundType type, Wood backWood, Wood topWood) {
        this.backWood = backWood;
        this.builder = builder;
        this.model = model;
        this.topWood = topWood;
        this.type = type;
    }
    // 属性略
}

解耦

由于GuitarSpec成为了一个独立的对象,所以,我们的Guitar类型只需要保存一个GuitarSpec对象就可以了:

public class Guitar {
    private string serialNumber;   // 序列号
    private double price;          // 价格
    private GuitarSpec spec;       // 吉他属性集
    // 略...
}

此处有一个地方值得注意, Guitar的构造函数通常会有下面两种写法:

public Guitar(string serialNumber, Builder builder, string model, SoundType type, Wood backWood, Wood topWood, double price) {
    this.price = price;
    this.serialNumber = serialNumber;
    this.spec = new GuitarSpec(builder, model, type, backWood, topWood);
}

public Guitar(string serialNumber, double price, GuitarSpec spec) {
    this.price = price;
    this.serialNumber = serialNumber;
    this.spec = spec;
}

采用第一种写法时,我们在Guitar类的构造函数中创建GuitarSpec类型实例,第二种在Guitar类外部先行创建好,然后再传入。那么采用那种方式好呢?我们回想一下,创建GuitarSpec的目的就是为了将易变化的部分从Guitar类中隔离出去,而采用第一种方式时,无异于再次将变化重新引入Guitar类,因为当我们向GuitarSpec类添加或删除属性时,必须同时修改Guitar类的构造函数!所以,这里我们采用第二种方式的构造函数。

类似的我们修改Inventory类的AddGuitar()方法和Search()方法:

// 向列表中添加 吉他
public void AddGuitar(string serialNumber, double price, GuitarSpec spec) {
    Guitar guitar = new Guitar(serialNumber, price, spec);
    guitarList.Add(guitar);
}

// 搜索吉他列表,寻找满足searchSpec参数的吉他
public List<Guitar> Search(GuitarSpec searchSpec) {
    List<Guitar>.Enumerator it = guitarList.GetEnumerator();

    List<Guitar> list = new List<Guitar>(); // 保存满足搜索条件的吉他
    while (it.MoveNext()) {
        GuitarSpec guitarSpec = it.Current.Spec;

        if (guitarSpec.Builder != searchSpec.Builder)
            continue;
        string model = searchSpec.Model.ToLower();
        if (!String.IsNullOrEmpty(model) &&
            !model.Equals(guitarSpec.Model.ToLower()))
            continue;
        if (guitarSpec.Type != searchSpec.Type)
            continue;
        if (guitarSpec.BackWood != searchSpec.BackWood)
            continue;
        if (guitarSpec.TopWood != searchSpec.TopWood)
            continue;

        list.Add(it.Current);  // 添加到列表中
    }

    return list;    // 返回结果
}

现在看上去程序已经完善的差不多了,我们上面做得这些都是为了能够在Guitar的属性变化的时候,尽可能的少做修改。检验程序是否经得起变化的一个方法就是我们现在假设删除一个属性model,看看需要改变哪些地方:我们得出Guitar类是不需要进行修改的,GuitarSpec类需要删除model属性,然而,我们发现Inventory类也需要进行修改,因为它的Search方法依赖于guitarSpec类的Model属性,因为要对它进行判断。此时,我们说Inventory类与GuitarSpec类是耦合在一起的。那么如何才能使得修改GuitarSpec类不需要改动Inventory类呢?我们可以将对GuitarSpec进行判等的操作,委托给GuitarSpec类型本身来完成,我们让GuitarSpec类实现IEquatable<T>接口:

public class GuitarSpec :IEquatable<GuitarSpec> {
    // 其余略...        
    public bool Equals(GuitarSpec other) {
        if (builder != other.Builder)
            return false;
        string model = other.Model.ToLower();
        if (!String.IsNullOrEmpty(model) &&
            ! model.Equals(this.model.ToLower()))
            return false;
        if (type != other.Type)
            return false;
        if (backWood != other.BackWood)
            return false;
        if (topWood != other.TopWood)
            return false;

        return true;
    }
}

现在判断两个GuitarSpec是否相等的逻辑转移到了GuitarSpec类型本身,我们再次修改Inventory的Search()方法,让它将对GuitarSpec的判等操作委托出去。

// 搜索吉他列表,寻找满足searchSpec参数的吉他
public List<Guitar> Search(GuitarSpec searchSpec) {
    List<Guitar>.Enumerator it = guitarList.GetEnumerator();

    List<Guitar> list = new List<Guitar>(); // 保存满足搜索条件的吉他
    while (it.MoveNext()) {
        GuitarSpec guitarSpec = it.Current.Spec;

        if (guitarSpec.Equals(searchSpec)) // 进行两个对象的判等
            list.Add(it.Current);  // 将结果添加到列表中
    }

    return list;    // 返回结果
}

经过现在的修改之后,不仅Search()方法的实现变得更为简单,各个类的职责也更加清晰,我们修改GuitarSpec类型也不会影响到Inventory类和Guitar类。

3.抽象和继承

接下来我们来对上面的程序进行一下扩展,假如我们的程序不仅需要对吉他(Guitar)进行管理和维护,还需要对曼陀林(Mandolin,一种琵琶乐器)进行管理,它的属性与吉他是类似的,但是多了一个Style属性,有"A"和"F"两种取值;同时我们为吉他再加入一个NumStrings属性,代表玹的数量,那么该如何改进程序呢?

首先我们创建一个Style枚举,它只包含A、F两个枚举值。接下来,我们可以将Guitar类和Mandolin的公共部分抽象出来,建立一个Instrument基类,这个Instrument基类包含Guitar和Mandolin公有的部分,然后让Guitar和Mandolin继承自Instrument。因为我们实际上并不需要创建一个Instrument的实例,所以我们将它声明为抽象的。类似的,我们将GuitarSpec也抽象为InstrumentSpec,并且再为Mandolin创建一个MandolinSpec类,让GuitarSpec和MandolinSpec继承自InstrumentSpec:

// Instrument乐器基类
public abstract class Instrument {
    private string serialNumber;   // 序列号
    private double price;          // 价格
    private InstrumentSpec spec;   // 乐器属性集

    // 构造函数和属性略
}
// 吉他类
public class Guitar:Instrument {
   public Guitar(string serialNumber, double price, GuitarSpec spec):base(serialNumber, price, spec) {
    }
}
// 曼陀林类
public class Mandolin:Instrument {
    public Mandolin(string serialNumber, double price, MandolinSpec spec)
        : base(serialNumber, price, spec) {
    }
}

以及InstrumentSpec和GuitarSpec、MandolinSpec类:

public abstract class InstrumentSpec : IEquatable<InstrumentSpec> {
    private Builder builder;       // 厂商
    private string model;          // 型号
    private SoundType type;        // 类型
    private Wood backWood;         // 后部材质
    private Wood topWood;          // 前面材质

    // 构造函数和属性略

    public bool Equals(InstrumentSpec other) {
        string model = other.Model.ToLower();
        if (!String.IsNullOrEmpty(model) &&
            ! model.Equals(this.model.ToLower()))
            return false;
        if (builder != other.Builder)
            return false;
        if (type != other.Type)
            return false;
        if (backWood != other.BackWood)
            return false;
        if (topWood != other.TopWood)
            return false;

        return true;
    }
}

public class GuitarSpec:InstrumentSpec, IEquatable<GuitarSpec> {
    private int numStrings;

    public GuitarSpec(Builder builder, string model, SoundType type, Wood backWood, Wood topWood, int numStrings)
        :base(builder, model,type,backWood, topWood) {
        this.numStrings = numStrings;
    }

    public int NumStrings{
        get { return numStrings; }
    }

    public bool Equals(GuitarSpec other) {
        if(!base.Equals(other))
            return false;
        if (numStrings != other.NumStrings)
            return false;

        return true;
    }
}

public class MandolinSpec : InstrumentSpec, IEquatable<MandolinSpec> {

    private Style style;

    public MandolinSpec(Builder builder, string model, SoundType type, Wood backWood, Wood topWood, Style style)
        : base(builder, model, type, backWood, topWood) {
        this.style = style;
    }

    public bool Equals(MandolinSpec other) {
        if (!base.Equals(other))
            return false;
        if (style != other.style)
            return false;

        return true;
    }
}

最后,我们需要修改Inventory类:

public class Inventory {
    private List<Instrument> instrumentList;// 维护现有的所有乐器

    public Inventory() {
        instrumentList = new List<Instrument>();
    }

    // Search() 和 AddInstrument()方法见下
}

我们通过抽象和继承完成了程序的扩展。现在来看一下上面实现的扩展性如何,为了更简单地对问题进行描述,我们设想如果再加入一种乐器,班卓琴(Banjo),程序需要做哪些改动?

1、我们需要再定义一个继承自Instrument的类Banjo;

2、以及一个继承自InstrumentSpec的类BanjoSpec;此时,如果BanjoSpec拥有InstrumentSpec没有定义的属性,那么很好办,我们在BanjoSpec中添加新增的属性即可;如果BanjoSepc不需要InstrumentSpec中定义的属性,比如说Model,那么就麻烦了,我们需要从InstrumentSpec中删掉此属性,然后再在InstrumentSpec除了BanjoSpec以外的所有子类中添加刚才删去的Model属性。

3、我们还需要修改Inventory的AddInstrument()方法:

// 向列表中添加 乐器
public void AddInstrument(string serialNumber, double price, InstrumentSpec spec) {
    Instrument instrument = null;
    if (spec is GuitarSpec){
        instrument = new Guitar(serialNumber, price, (GuitarSpec)spec);
    } else if (spec is MandolinSpec) {
        instrument = new Mandolin(serialNumber, price, (MandolinSpec)spec);
    }
    instrumentList.Add(instrument);
}

这里,因为Instrument是抽象类,所以我们无法创建Instrument的实例,只能创建其子类的实例,而Guitar和Mandolin的构造函数,分别需要InstrumentSpec的子类(GuitarSpec和MandolinSpec),所以我们需要先进行向下转换((GuitarSpec)spec),才能创建对象。

4、类似地,我们也需要修改Search()方法:

// 搜索列表,寻找满足SearchSpec参数的乐器
public List<Instrument> Search(InstrumentSpec searchSpec) {
    List<Instrument>.Enumerator it = instrumentList.GetEnumerator();

    List<Instrument> list = new List<Instrument>();
    MandolinSpec mandolinSpec;
    GuitarSpec guitarSpec;
    while (it.MoveNext()) {
        if (it.Current is Guitar && searchSpec is GuitarSpec) {
            guitarSpec = (GuitarSpec)it.Current.Spec;
            if (guitarSpec.Equals((GuitarSpec)searchSpec))
                list.Add(it.Current);
        } else if (it.Current is Mandolin && searchSpec is MandolinSpec) {
            mandolinSpec = (MandolinSpec)it.Current.Spec;
            if (mandolinSpec.Equals((MandolinSpec)searchSpec))
                list.Add(it.Current);
        }
    }

    return list;
}

我们看到,尽管只是添加一种乐器,不仅需要对多处进行修改,而且还要再添加两个新类Banjo和BanjoSpec。设想如果有10多种乐器,那么改动及类的数量都会是非常多的,维护起来也会像是噩梦一般。那么下来该再如何改进呢?我们接着往下看。

4.动态属性

首先我们看一下Guitar、Mandolin和Banjo类,它们除了构造函数不同以外其余完全相同。而一般情况下,我们定义一个抽象类和子类这种继承体系,目的是为了在基类中实现一种行为,然后在各个子类中对其进行重写,以实现多态的效果。所以,此处我们可以考虑另外一种方式,将Instrument声明为实例的,并且在其中加入一个枚举类型的属性InstrumentType,由这个属性来标识乐器的类别。以后我们需要添加新的类型,只需要在这个枚举中添加就可以了:

// 乐器类型
public enum InstrumentType {
    Guitar = 0, Mandolin, Banjo
}

因为InstrumentType和SerialNumber、Price一样,属于每种乐器都有的属性,所以我们将它定义在Instrument类中,而非InstrumentSpec中,此时Instument我们也声明为一般类,而非抽象类:

public class Instrument {
    private InstrumentType type;   // 乐器类型

    // 其余略...
}

对于InstrumentSpec类及其子类而言,由于属性是多变的,而基类并没有定义抽象或者虚拟方法供子类覆盖,所以我们可以使用一个Hashtable将乐器的属性值按照 key/value 的形式保存起来,其中 key是属性名称,value是属性值。这样就可以删去所有的InstrumentSpec的子类(GuitarSpec、MandolinSepc等),同时,我们将InstrumentSpec声明为一般类:

public class InstrumentSpec : IEquatable<InstrumentSpec> {

    private Hashtable properties;

    public InstrumentSpec(Hashtable properties) {
        if (properties == null)
            properties = new Hashtable();
        else
            this.properties = properties;
    }

    public Hashtable Properties {
        get { return properties; }
    }

    public Object GetProperty(object propertyName) {
        return properties[propertyName];
    }

    public bool Equals(InstrumentSpec other) {
        IEnumerator it = other.properties.Keys.GetEnumerator();
        while (it.MoveNext()) {
            if (properties[it.Current] != other.properties[it.Current])
                return false;
        }

        return true;
    }
}

通过上面的改变,我们添加新乐器时,只需要改变枚举就可以了,而不需要再添加大量的诸如Guitar和GuitarSpec这样的子类。

最后我们再看一下Inventory类的实现:

public class Inventory {
    private List<Instrument> instrumentList;// 维护现有的所有乐器

    public Inventory() {
        instrumentList = new List<Instrument>();
    }

    // 向列表中添加 乐器
    public void AddInstrument(string serialNumber, double price, InstrumentSpec spec) {
        Instrument instrument = new Instrument(serialNumber, price, spec);
        instrumentList.Add(instrument);
    }

    // 搜索列表,寻找满足SearchSpec参数的乐器
    public List<Instrument> Search(InstrumentSpec searchSpec) {
        List<Instrument>.Enumerator it = instrumentList.GetEnumerator();
        List<Instrument> list = new List<Instrument>();

        while (it.MoveNext()) {
            if (it.Current.Spec.Equals(searchSpec))
                list.Add(it.Current);
        }
        return list;
    }
}

可以看到Inventory类也变得清爽了许多。那么采用这种方式是不是就最好了呢?我们仍然要看到它的问题:

  1. 尽管将属性和属性值保存在Hashtable中极大的增加了灵活性,但是我们每次构建对象,为对象添加属性值也会变得非常繁琐。
  2. Hashtable返回的是一个Object类型的对象,所以我们在获得到属性之后,还需要再进行一次向下转换才行。
  3. 同样,因为Hashtable可以接收任何类型的对象,所以我们也就丧失了类型安全,比如说,对于一个只可以接受int类型的属性,我们可以输入任意值而在编译时不会报错,只有在运行时,我们将值取出进行向下转化时才会抛出异常。

所以说,设计并没有最好,只有最合适的,本文讨论的也是一样,我们只能根据实际情况,选择最合适的解决方案。对于 只有一种乐器、支持多种乐器、乐器属性变化不大、属性变化很大等各种不同情况,我们需要做出权衡,选择合适的解决方案。另外在实现时还要做出一定的预见,考虑以后某方面的变更会不会很大,然后再考虑需不需要留出扩展的余地。对于一个系统,我们很可能 设计不足,也有可能 过度设计。我觉得,我们应该首先具备了 过度设计 的能力,然后再去考虑哪些地方不需要过度“灵活”,因为通常每种设计都有着自身的优点和缺陷,很难找到一种绝对正确的方案

转载于:https://www.cnblogs.com/lenoevo/archive/2008/09/24/1297612.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363535.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java SE 8新特性导览:使用Lambda Expression进行函数式编程

“ Java SE 8新功能浏览 ”系列的这篇文章将深入了解Lambda表达式 。 我将向您展示Lambda表达式的几种不同用法。 它们都具有功能接口的共同实现。 我将解释编译器如何从代码中推断信息&#xff0c;例如特定类型的变量以及后台实际发生的情况。 在上一篇文章“ Java SE 8新功能…

停止尝试使用内部DB框架模拟SQL OFFSET分页!

我敢肯定&#xff0c;到目前为止&#xff0c;您已经以多种方式弄错了。 而且您可能很快将无法正确处理。 那么&#xff0c;当您可以实施业务逻辑时&#xff0c;为什么还要在SQL调整上浪费您的宝贵时间呢&#xff1f; 让我解释… 直到最近的SQL&#xff1a;2008标准 &#xff0…

和朱晔一起复习Java并发(五):并发容器和同步器

和朱晔一起复习Java并发&#xff08;五&#xff09;&#xff1a;并发容器和同步器 本节我们先会来复习一下java.util.concurrent下面的一些并发容器&#xff0c;然后再会来简单看一下各种同步器。 ConcurrentHashMap和ConcurrentSkipListMap的性能 首先&#xff0c;我们来测试一…

Hive:使用Apache Hive查询客户最喜欢的搜索查询和产品视图计数

这篇文章涵盖了使用Apache Hive查询存储在Hadoop下的搜索点击数据。 我们将以示例的方式生成有关总产品浏览量的客户排名靠前的搜索查询和统计信息。 继续之前的文章 使用大数据分析客户产品搜索点击次数 &#xff0c; Flume&#xff1a;使用Apache Flume收集客户产品搜索点…

expdp错误案例

转自:https://www.cnblogs.com/kerrycode/p/3960328.html Oracle数据泵(Data Dump)使用过程当中经常会遇到一些奇奇怪怪的错误案例&#xff0c;下面总结一些自己使用数据泵(Data Dump)过程当中遇到的问题以及解决方法。都是在使用过程中遇到的问题&#xff0c;以后陆续遇到数据…

HashSet源码分析:JDK源码系列

1.简介 继续分析源码&#xff0c;上一篇文章把HashMap的分析完毕。本文开始分析HashSet简单的介绍一下。 HashSet是一个无重复元素集合&#xff0c;内部使用HashMap实现&#xff0c;所以HashMap的特征耶继承了下来。存储的元素是无序的并且HashSet允许使用空的元素。 HashSet是…

修改左侧导航显示样式(转载自Sunmoonfire's artistic matrix)

这是一片非常好的文章&#xff0c;修改下CSS就可以改变左侧导航栏的样式&#xff0c;在网上找了一些都是要写代码的。怕连接失效&#xff0c;所以直接将文章考了过来&#xff0c;希望作者原谅&#xff0c;如有不妥&#xff0c;请通知一声&#xff0c;我会将文章删掉&#xff01…

tf.argmax()以及axis

tf.argmax()表示返回最大值的索引号&#xff0c;axis为0 &#xff0c;表示返回每列最大值索引号。axis为1 &#xff0c;表示返回每行最大值索引号 结果为 转载于:https://www.cnblogs.com/san333/p/10507402.html

jquery ajax 上传文件 demo,Jquery+AJAX上传文件,无刷新上传并重命名文件

index.htmlAjax上传图片Ajax上传图片function upload(){var form new FormData(document.getElementById("form"));$.ajax({url:"upload.php",type:"post",data:form,cache: false,processData: false,contentType: false,success:function(dat…

Meet Fabric8:基于Camel和ActiveMQ的开源集成平台

面料8 Fabric8是Red Hat的JBoss Fuse产品的Apache 2.0许可上游社区。 这是一个基于Apache ActiveMQ &#xff0c; Camel &#xff0c; CXF &#xff0c; Karaf &#xff0c; HawtIO等的集成平台。 它提供了自动化的配置和部署管理&#xff0c;以帮助使部署变得容易&#xff0…

Django之web框架的本质

web框架的本质及自定义web框架 我们可以这样理解&#xff1a;所有的Web应用本质上就是一个socket服务端&#xff0c;而用户的浏览器就是一个socket客户端&#xff0c;基于请求做出响应&#xff0c;客户都先请求&#xff0c;服务端做出对应的响应&#xff0c;按照http协议的请求…

Springboot 系列(十三)使用邮件服务

在我们这个时代&#xff0c;邮件服务不管是对于工作上的交流&#xff0c;还是平时的各种邮件通知&#xff0c;都是一个十分重要的存在。Java 从很早时候就可以通过 Java mail 支持邮件服务。Spring 更是对 Java mail 进行了进一步的封装&#xff0c;抽象出了 JavaMailSender. 后…

服务器能否只做c盘系统,我的云服务器只有一个c盘

我的云服务器只有一个c盘 内容精选换一换检查Pkey是否一致。查看弹性云服务器内部分配到的Pkey&#xff1a;cat /sys/class/infiniband/mlx5_0/ports/1/pkeys/* | grep -v "0x0000"检查Pkey是否一致如果环境中查出来的Pkey只有一个&#xff0c;请联系技术支持人员。如…

单例模式(C++实现)

RAII运用 只能在栈上创建对象 只能在堆上创建的对象 单例模式 设计模式 懒汉模式 解决线程安全 优化 饿汉模式 饿汉和懒汉的区别

Flume:使用Apache Flume收集客户产品搜索点击数据

这篇文章涵盖了使用Apache flume收集客户产品搜索点击并使用hadoop和elasticsearch接收器存储信息。 数据可能包含不同的产品搜索事件&#xff0c;例如基于不同方面的过滤&#xff0c;排序信息&#xff0c;分页信息&#xff0c;以及进一步查看的产品以及某些被客户标记为喜欢的…

vue-cli使用swiper4在ie以及safari报错

vue-cli项目中&#xff0c;通过npm run swiper --save-dev安装的是swiper4版本的插件&#xff0c;这样安装以后在谷歌火狐等浏览器都可以正常运行&#xff0c;但是在safari浏览器&#xff08;可能是版本太低&#xff09;还有ie&#xff08;9,10,11&#xff09;打开会报错&#…

电脑内部,小贴士:电脑内部连接标准

小贴士&#xff1a;电脑内部连接标准在介绍电脑内部连接标准之前&#xff0c;首先应该了解一下电脑内部接线的种类&#xff0c;以便分类处置。电脑内部尽管五颜六色的导线&#xff0c;其中导线的种类可以分为3 类&#xff0c;即电源线、信号线和控制线&#xff0c;而控制线又常…

太快了,太变态了:什么会影响Java中的方法调用性能?

那么这是怎么回事&#xff1f; 让我们从一个简短的故事开始。 几周前&#xff0c;我提议对Java核心libs邮件列表进行更改 &#xff0c;以覆盖当前final一些方法。 这刺激了一些讨论主题-其中之一是其中一个性能回归通过采取这是一个方法被引入的程度final免遭停止它final 。 我…

1、dubbo的概念

Dubbo是什么&#xff1f; Dubbo是阿里巴巴SOA服务化治理方案的核心框架&#xff0c;每天为2,000个服务提供3,000,000,000次访问量支持&#xff0c;并被广泛应用于阿里巴巴集团的各成员站点。Dubbo[]是一个分布式服务框架&#xff0c;致力于提供高性能和透明化的RPC远程服务调用…

轻云服务器的性能,腾讯云轻量应用服务器性能评测(以香港地域为例)

腾讯云轻量应用服务器香港节点24元/月&#xff0c;价格很不错&#xff0c;ForeignServer来说说腾讯云轻量服务器香港地域性能评测&#xff0c;包括腾讯云轻量应用服务器CPU型号配置、网络延迟速度测试&#xff1a;腾讯云香港轻量应用服务器性能评测腾讯云轻量应用服务器地域可选…