设计模式名称:ClassA的对象就像ClassB的对象的“类”

时间:2018-12-11 12:53:00

标签: oop design-patterns

我有两个类*,我不确定如何称呼它们之间的关系。 ClassA 的对象类似于 ClassB 的对象的类(“原型”或“模式”)。

  • Song有标题和艺术家。
  • 一个SongDimension ClassA )具有一个标签(例如“流派”;“响度”)和一组AcceptableValue的标签(例如“经典摇滚”, “古典”,“雷鬼”,“ 9”,“ 10”,“ 11”)。
  • 一个Song有一组SongDimensions,对于每个SongDimension,它恰好有一个SongDimensionValue ClassB ),该AcceptableValue的哪个SongDimension适用于此Song

我正在尝试使用这种模式(或替代方法,如果这不是一个好的模式)来寻找最佳实践,但是我不知道该怎么称呼。在我看来,SongDimension的对象几乎就像SongDimensionValue的对象的类。但是,当我使用这样的术语搜索Internet时,只会得到有关什么是类和对象的基本文章。

此模式有名称吗?

*我正在使用Java术语(类,对象),但是此模式可以在任何OOP语言中应用。

编辑:谢谢大家的回答。阅读它们,我意识到我忘了提到一个重要的要求。我希望用户能够在运行时创建自己的SongDimension实例,而我在编译时无法预测这些实例。

例如,系统的一个用户可能不愿意使用流派SongDimension在其库中对Songs进行分类。而是使用标签“颜色”创建自己的SongDimension,并为其提供可接受的值,例如红色,黄色和黑色。这个新的SongDimension对其他用户可能没有任何意义,但对他们来说却是有意义的,这就是我要允许的灵活性。他们仍然可以独立于流派而使用响度SongDimension

如果不是这个要求,枚举和继承确实是可行的方法。如果不首先包含它,那对我来说是不好的。

4 个答案:

答案 0 :(得分:0)

听起来您正在寻找枚举。

SongDimension是一个界面。 GenreLoudness等是实现接口的枚举类。可接受的值定义为各个枚举的实例。

这很适合您的描述:SongDimension的具体实现是枚举类,其实例对象定义了可接受值的范围。 Java特别适合这种设计,因为Java的枚举类是一类公民,它像其他任何类一样支持状态和行为。

这不是一种设计模式,但是它是Java中常见的编码习惯。

public class Main {
    public static void main(String... args) {
        Song mySong = new Song("My Favorite Song", "My Favorite Band",
                               Genre.Classic_Rock, Loudness.Eleven);
        System.out.println(mySong);
    }

    static class Song {
        final String title;
        final String artist;
        final Collection<SongDimension> dimensions = new HashSet<>();

        Song(String title, String artist, SongDimension... dimensions) {
            this.title = title;
            this.artist = artist;
            this.dimensions.addAll(Arrays.asList(dimensions));
        }
        @Override public String toString() {
            return "Song{ title=" + title + ", artist=" + artist +
                       ", dimensions=" + dimensions + " }";
        }
    }

    interface SongDimension {
        String label();
        String value();
    }

    enum Genre implements SongDimension {
        Classic_Rock, Classical, Reggae;

        @Override public String label() {
            return getDeclaringClass().getSimpleName();
        }
        @Override public String value() {
            return name().replace('_',' ');
        }
        @Override public String toString() {
            return label() + ':' + value();
        }
    }

    enum Loudness implements SongDimension {
        Nine(9), Ten(10), Eleven(11);

        final int volume;
        Loudness(int volume) {
            this.volume = volume;
        }

        @Override public String label() {
            return getDeclaringClass().getSimpleName();
        }
        @Override public String value() {
            return String.valueOf(volume);
        }
        @Override public String toString() {
            return label() + ':' + value();
        }
    }
}

答案 1 :(得分:0)

TL; DR;您似乎在描述一个常见的继承问题。有一个SongDimension概念,还有其他不同的概念,它们都共享它们是SongDimension的子类型的事实。即响度是歌曲的一个维度,节奏,类型等也是歌曲的维度。查找多态性和继承性。

现在给出更长的答案。

巨大的免责声明!

请原谅任何Java语法错误。您似乎最熟练地使用Java,所以即使我不是经常使用Java的程序员,我也认为最好在答案中讲Java。但是一般模式适用于多种语言。

在答案的结尾,我使用了一些概念,这些概念比我应该说的要先进得多,但这可能是鼓舞人心的。


在这种情况下,我希望保持尽可能多的通用性。我还希望能够施加任何有意义的限制,例如您建议的某些允许值,以及关于允许的计算方式。

我会想到这样的问题:

有一个Song类。每首歌曲都是该类的一个实例。

歌曲类具有SongDimension类型的对象的集合:

public class Song {
    public string title;
    public string artist;
    public List<SongDimension> songDimensions;
}

SongDimension类是具有以下签名的抽象类

public abstract class SongDimension {
    public abstract string GetValue();
}

然后,对于每种类型的尺寸,您可能想要为可接受的值生成枚举,或者应用自定义逻辑,例如在响度方面,如下所示:

public enum Genres {
    ClassicRock, Classical, Reggae
}


public class Genre extends SongDimension {
    private Genres _genre;

    public string GetValue(){
        switch(_genre) {
           case ClassicRock: return "Classic Rock";
           default: return _genre.name();
        }
    }

    public Genre(Genres whichGenre) {
        _genre = whichGenre;
    }
}

public class Loudness extends SongDimension {
    private int _loudness;

    public string GetValue(){
        return String.valueOf(_loudness);
    }

    public int GetIntValue() {
        return _loudness;
    }

    public Loudness(int howLoud) {
        if(howLoud < 9 || howLoud > 11) throw new IllegalArgumentException();

        _loudness = howLoud;
    }

    // Add more relevant operations on the value here,
    // maybe you can take differences of loudnesses for instance?
}

要使用全部功能,您可以创建几首这样的歌曲:

Song EineKleineNachtMusik = new Song();
EineKleineNachtMusik.songDimensions.Add(new Genre(Genres.Classical));
EineKleineNachtMusik.songDimensions.Add(new Loudness(9));

Song BackInBlack = new Song();
BackInBlack.songDimensions.Add(new Genre(Genres.ClassicRock));
BackInBlack.songDimensions.Add(new Loudness(11));

for(SongDimensions d : EineKleineNachtMusik.songDimensions){
    System.out.println(d.GetValue());
}
/// prints:
/// Classical
/// 9

所有这些功能的最大价值在于,它可以使您获得有关歌曲的各种信息,而不仅仅是字符串,而是适当的对象。对值和类型使用字符串占位符的风险是,最终可能对“ 11”的真正含义有不同的解释。即使您可能只能将值“ 9”,“ 10”或“ 11”指定为“响度”,也没有阻止您以后将“ 10”视为完全不同的错误,例如歌曲长度(以分钟为单位)。

通过这种方式,我们可以创建语义上合理的对象,并将自定义逻辑合并到不同种类的特征中。例如,我们可以让Loudness类实现Comparable<Loudness>(肯定是天真的实现。例如不检查null):

public class Loudness extends SongDimension implements Comparable<Loudness> {
    ...
    // Same as before with the addition of the following:
    @Override public int compareTo(Loudness that) {
        if(_loudness < that.GetIntValue()) return -1;
        if(_loudness == that.GetIntValue()) return 0;
        if(_loudness > that.GetIntValue()) return 1;
    }  
}

既然我不是Java开发人员,那么我现在将步履蹒跚。这将最有可能包含错误,因此,请随时猛击所有您想要的注释。但这进一步说明了一点,即通过使用一些最小的样板,可以立即获得很多好处,为尺寸强制设置适当的值,实现自定义的适当逻辑,每种类型的尺寸都不同,等等。

Song类中添加了一些辅助方法来检索特定类型的尺寸:

 public class Song {
    ...
    // same as above, with the addition of:

    public <T extends SongDimension> T GetDimension(Class<T> dimType) {
       return songDimensions.stream().filter(dim -> dim instanceof 
            dimType).Collect(Collectors.toList()).iterator().next()
    }
}

然后我们可以比较两首这样的歌曲:

Loudness l1 = EineKleineNachtMusik.GetDimension(Loudness.class);
Loudness l2 = BackInBlack.GetDimension(Loudness.class);

if(l1 != null && l2 != null && l1.compareTo(l2) < 0) {
   System.out.println("Yup, BackInBlack is the louder of the two");
}

答案 2 :(得分:0)

我尝试提供不需要任何Enum的解决方案,因为您正在寻找:

  

我使用Java术语(类,对象),但是这种模式可以在任何OOP语言中应用。

也许是逻辑错误?

给定的问题可能包括逻辑错误:

  

一首歌有一套SongDimensions
  [...]
  SongDimension(ClassA)具有标签(例如“流派”;“响度”)

这意味着Song对象可能同时具有多个具有不同响度的不同流派。

但是,如果您在代码中思考,那么您真正想说的是new Song("mj", "billie jean", new Rock(), new LoudnessLevel8());

但是在给定的问题中, Billie jean 可能是响度 6 reggae ,而同时是 >摇滚,响度为8

如果我错了,请在评论中纠正我。

无逻辑错误的解决方案

歌曲

  

一首歌有标题和歌手。一首歌曲具有一组 SongDimensions。

class Song {
  private String title;
  private String artist;
  private SongDimension songDimension;
}

SongDimension

对于SongDimension,我们需要GenreLoudness。为此,我们可以创建一个抽象类型,而不是多个具体类型。以下是Genre的示例:

interface Genre { /**/ }

class Classic implements Genre { /**/ }

class Rock implements Genre { /**/ }

这些具体类型(例如ClassicRock)是您的AcceptableValue

让我们尝试制作一首新歌:

SongDimension rockBy8= new SongDimension(new Rock(), new LoudnessLevel8());
Song billieJean = new Song("mj", "billie jean", rockBy8);

SongDimensionValue

我认为不需要SongDimensionValue ..在评论中纠正我

答案 3 :(得分:0)

这听起来像power type。也许尝试这样的事情:

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
class Artist {}
class Song {
    Song(String title,Artist artist,Map<Dimensions,Object> dimensionToValue) {
        this.title=title;
        this.artist=artist;
        this.dimensionToValue=dimensionToValue;
    }
    void setDimensionValue(Dimensions dimension,Object object) {
        try {
            if(dimensionToValue.containsKey(dimension)) {
                if(dimension.dimensionValues.contains(object)) {
                    System.out.println("set "+dimension+" to "+object);
                    dimensionToValue.put(dimension,object);
                }
                else throw new RuntimeException(object+" is illegal!");
            } else throw new RuntimeException(dimension+"is illegal dimension! for this song?");
        } catch(Exception e) {
            System.out.println("caught: "+e);
        }
    }
    Object getDimensionValue(Dimensions dimension) {
        return dimensionToValue.get(dimension);
    }
    String title;
    Artist artist;
    final Map<Dimensions,Object> dimensionToValue;
}
class Data {
    static final String[] genreValuesArray=new String[] {"country","rock"};
    static final Integer[] loudnessValuesArray=new Integer[] {1,2,3};
    static final Set<Object> genreValues=new LinkedHashSet(Arrays.asList(genreValuesArray));
    static final Set<Object> loudnessValues=new LinkedHashSet(Arrays.asList(loudnessValuesArray));
}
enum Dimensions {
    genre(Data.genreValues),loudness(Data.loudnessValues);
    Dimensions(Set<Object> dimensionValues) {
        this.dimensionValues=dimensionValues;
    }
    final Set<Object> dimensionValues;
}
public class So53724633_design_pattern_name_object_of_classa_acts_like_a_class_for_objects_of_classb {
    public static void main(String[] args) {
        Map<Dimensions,Object> map=new LinkedHashMap<>();
        Dimensions dimension=Dimensions.genre;
        map.put(dimension,null);
        Song song1=new Song("foo",new Artist(),map);
        Object value=song1.getDimensionValue(dimension);
        System.out.println("value of "+dimension+" is: "+value);
        song1.setDimensionValue(Dimensions.genre,Data.genreValuesArray[0]);
        value=song1.getDimensionValue(dimension);
        System.out.println("value of "+dimension+" is: "+value);
        song1.setDimensionValue(Dimensions.genre,"classical");
        song1.setDimensionValue(Dimensions.loudness,Integer.valueOf(1));
    }
}