内容简介:实现这样一个类中使用在下面的程序中,我们期望
- 在包的内部是用继承,不存在跨包继承。
- 专门为了扩展而设计,并且具备很好的文档说明。
一个例子
实现这样一个 HashSet ,可以跟踪从它被创建之后曾经添加过几个元素。
使用继承实现
public class InstrumentedSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedSet() {
}
public InstrumentedSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
复制代码
类中使用 addCount 字段记录添加元素的次数,并覆盖父类的 add() 和 addAll() 实现,对 addCount 字段进行设值。
在下面的程序中,我们期望 getAddCount() 返回3,但实际上返回的是6。
InstrumentedSet<String> s = new InstrumentedSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
复制代码
问题出在于:在 HashSet 中, addAll() 的实现是基于 add() 方法的。子类在扩展父类的功能时,如果不清楚实现细节,是非常危险的,况且父类的实现在未来可能是变化的,毕竟它并不是为扩展而设计的。
使用组合实现
不用扩展现有的类,而是在新的类中增加一个私有字段,引用现有类的实例。这种设计被叫做 组合 。
先创建一个干净的 SetWrapper 组合类。
public class SetWrapper<E> implements Set<E> {
private final Set<E> s;
public SetWrapper(Set<E> s) { this.s = s; }
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator<E> iterator() { return s.iterator(); }
public boolean add(E e) { return s.add(e); }
public boolean remove(Object o){ return s.remove(o); }
public boolean containsAll(Collection<?> c) { return s.containsAll(c); }
public boolean addAll(Collection<? extends E> c) { return s.addAll(c); }
public boolean removeAll(Collection<?> c) { return s.removeAll(c); }
public boolean retainAll(Collection<?> c) { return s.retainAll(c); }
public Object[] toArray() { return s.toArray(); }
public <T> T[] toArray(T[] a) { return s.toArray(a); }
@Override public boolean equals(Object o) { return s.equals(o); }
@Override public int hashCode() { return s.hashCode(); }
@Override public String toString() { return s.toString(); }
}
复制代码
SetWrapper 实现了装饰模式,通过引用 Set<E> 类型的字段,面向接口编程,相比直接继承 HashSet 类来得更灵活。可以在调用该类的构造方法中传入任意 Set 具体类。扩展该类以实现需求。
public class InstrumentedSet<E> extends SetWrapper<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
复制代码
举一反三
注:以下代码均是伪代码,组合方式的实现待封装成 Android 库并开源,敬请期待。没错,这里打了个广告。
笔者曾开发的某个应用有以下2张截图:
详情页面和评论列表页面均复用了评论项的实现。
评论列表页面的 GameComentsAdapter 。
public class GameCommentsAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private static final int ITEM_TYPE_COMMENT = 1;
private List<Object> mDataSet;
@Override
public int getItemViewType(int position) {
Object item = getItem(position);
if (item instanceof Comment) {
return ITEM_TYPE_COMMENT;
}
return super.getItemViewType(position);
}
protected Object getItem(int position) {
return mDataSet.get(position);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == ITEM_TYPE_COMMENT) {
View itemView = inflater.inflate(R.layout.item_comment, parent, false);
return new CommentViewHolder(itemView);
}
return null;
}
@Override
public int getItemCount() {
return mDataSet.size();
}
}
复制代码
if-else 方式实现
修改 GameComentsAdapter 类,增加对游戏详情项的适配支持。
public class GameCommentsAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private static final int ITEM_TYPE_COMMENT = 1;
private static final int ITEM_TYPE_GAME_DETAIL = 2;
private List<Object> mDataSet;
@Override
public int getItemViewType(int position) {
Object item = getItem(position);
if (item instanceof Comment) {
return ITEM_TYPE_COMMENT;
}
if (item instanceof GameDetail) {
return ITEM_TYPE_GAME_DETAIL;
}
return super.getItemViewType(position);
}
protected Object getItem(int position) {
return mDataSet.get(position);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == ITEM_TYPE_COMMENT) {
View itemView = inflater.inflate(R.layout.item_comment, parent, false);
return new CommentViewHolder(itemView);
}
if (viewType == ITEM_TYPE_GAME_DETAIL) {
View itemView = inflater.inflate(R.layout.item_game_detail, parent, false);
return new GameDetailViewHolder(itemView);
}
return null;
}
@Override
public int getItemCount() {
return mDataSet.size();
}
}
复制代码
在游戏详情页面为 RecyclerView 创建一个 GameCommentsAdapter 对象。但该方式会让 GameCommentsAdapter 变得臃肿,也不满足OCP开闭原则。
继承方式实现
扩展一个 Adapter 至少要实现 getItemViewType() 、 onCreateViewHolder() 等方法,为了复用 GameComentsAdapter 类中对评论项,详情页面的 GameDetailAdapter 继承该类。
class GameDetailAdapter extends GameCommentsAdapter {
private static final int ITEM_TYPE_GAME_DETAIL = 2;
@Override
public int getItemViewType(int position) {
Object item = getItem(position);
if (item instanceof GameDetail) {
return ITEM_TYPE_GAME_DETAIL;
}
return super.getItemViewType(position);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_GAME_DETAIL) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.item_game_detail, parent, false);
return new GameDetailViewHolder(itemView);
}
return super.onCreateViewHolder(parent, viewType);
}
}
复制代码
突然来了一个新需求
产品希望在详情页面添加推荐项,复用首页列表项,如下图所示:
实现效果如下图所示:
Java 是单继承的, GameDetailAdapter 已经继承了 GameComentsAdapter 类了,无法再继承 HomeAdapter 。
难道继续在 GameComentsAdapter 类中增加 if 判断?
组合方式
先把各个列表项的创建、绑定等抽离出来,引入 Subadapter 类。
interface Subadapter {
boolean isFor(Object item);
int getLayoutId();
BaseViewHolder onCreateViewHolder(View itemView);
}
复制代码
实现 CommentSubadapter 。
class CommentSubadapter implements Subadapter {
@Override
public boolean isFor(Object item) {
return item instanceof Comment;
}
@Override
public int getLayoutId() {
return R.layout.item_comment;
}
@Override
public BaseViewHolder onCreateViewHolder(View itemView) {
return new CommentViewHolder(itemView);
}
}
复制代码
实现 GameDetailSubadapter 。
class GameDetailSubadapter implements Subadapter {
@Override
public boolean isFor(Object item) {
return item instanceof GameDetail;
}
@Override
public int getLayoutId() {
return R.layout.item_game_detail;
}
@Override
public BaseViewHolder onCreateViewHolder(View itemView) {
return new GameDetailViewHolder(itemView);
}
}
复制代码
实现 SubadapterManager 。
class SubadapterManager {
private SparseArray<Subadapter> mSubadapters = new SparseArray<>();
public void addSubadapter(int itemType, Subadapter subadapter) {
mSubadapters.put(itemType, subadapter);
}
public int getItemViewType(Object item) {
for (int i = 0; i < mSubadapters.size(); i++) {
Subadapter subadapter = mSubadapters.get(i);
if (subadapter.isFor(item)) {
return mSubadapters.keyAt(i);
}
}
return 0;
}
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int type) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
Subadapter subadapter = mSubadapters.get(type);
View itemView = inflater.inflate(subadapter.getLayoutId(), parent, false);
return subadapter.onCreateViewHolder(itemView);
}
}
复制代码
实现唯一的 Adapter 。
class Adapter extends RecyclerView.Adapter<BaseViewHolder> {
private List<Object> mDataSet;
private SubadapterManager mSubadapterManager;
protected Object getItem(int position) {
return mDataSet.get(position);
}
public void addSubadapter(int itemType, Subadapter subadapter) {
mSubadapterManager.addSubadapter(itemType, subadapter);
}
@Override
public int getItemViewType(int position) {
Object item = getItem(position);
return mSubadapterManager.getItemViewType(item);
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return mSubadapterManager.onCreateViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) {
holder.bind(getItem(position));
}
@Override
public int getItemCount() {
return mDataSet.size();
}
public void setDataSet(List<Object> set) {
mDataSet = set;
}
}
复制代码
组合起来使用。在评论列表页面,创建一个 Adapter 实例,并添加评论项功能。
List<Object> dataSet = new ArrayList<>(); dataSet.add(new Comment()); dataSet.add(new GameDetail()); Adapter adapter = new Adapter(); adapter.addSubadapter(ITEM_TYPE_COMMENT, new CommentSubadapter()); adapter.addSubadapter(ITEM_TYPE_GAME_DETAIL, new GameDetailSubadapter()); adapter.setDataSet(dataSet); 复制代码
实现 GameSubadapter 完成新需求。
class GameSubadapter implements Subadapter {
@Override
public boolean isFor(Object item) {
return item instanceof Game;
}
@Override
public int getLayoutId() {
return R.layout.item_game;
}
@Override
public BaseViewHolder onCreateViewHolder(View itemView) {
return new GameViewHolder(itemView);
}
}
复制代码
在游戏详情页面,创建一个 Adapter 实例,并添加游戏项功能。
List<Object> dataSet = new ArrayList<>(); dataSet.add(new Comment()); dataSet.add(new GameDetail()); dataSet.add(new Game()); Adapter adapter = new Adapter(); adapter.addSubadapter(ITEM_TYPE_COMMENT, new CommentSubadapter()); adapter.addSubadapter(ITEM_TYPE_GAME_DETAIL, new GameDetailSubadapter()); adapter.addSubadapter(ITEM_TYPE_GAME, new GameSubadapter()); adapter.setDataSet(dataSet); 复制代码
当某个页面不再支持评论项时,我们只要删除以下代码即可,不会修改到其他地方,满足OCP设计原则。
dataSet.add(new Comment()); adapter.addSubadapter(ITEM_TYPE_COMMENT, new CommentSubadapter()); 复制代码
延伸
在 Java 生态圈之外,有不少组合优于继承的实践。
Kotlin
Kotlin 语言有 delegation 机制,可以方便开发者使用组合。
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print()
}
复制代码
Kotlin 版 InstrumentedHashSet
class InstrumentedHashSet<E>(val set: MutableSet<E>)
: MutableSet<E> by set {
private var addCount : Int = 0
override fun add(element: E): Boolean {
addCount++
return set.add(element)
}
override fun addAll(elements: Collection<E>): Boolean {
addCount += elements.size
return set.addAll(elements)
}
}
复制代码
Go
Go 语言没有继承机制,通过原生支持组合来实现代码的复用。以下分别是 Reader 和 Writer 接口定义。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
复制代码
通过组合可以定义出具备读取和写入的新类型。
type ReadWriter interface {
Reader
Writer
}
复制代码
上述的例子是接口组合,也可以是实现组合。 (下面的例子来自 Go in Action 一书)
type user struct {
name string
email string
}
// notify implements a method that can be called via
// a value of type user.
func (u *user) notify() {
fmt.Printf("Sending user email to %s<%s>\n",
u.name,
u.email)
}
// admin represents an admin user with privileges.
type admin struct {
user // Embedded Type
level string
}
// main is the entry point for the application.
func main() {
// Create an admin user.
ad := admin{
user: user{
name: "john smith",
email: "john@yahoo.com",
},
level: "super",
}
// We can access the inner type's method directly.
ad.user.notify()
// The inner type's method is promoted.
ad.notify()
}
复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 欺骗技术优于蜜罐的8个理由
- 哈希图:它会优于区块链吗?
- android – RxJava2优于AsyncTask
- 随机森林算法预测法官判决,准确度优于人类水平
- Swift 中的面向协议编程:是否优于面向对象编程?
- V神再谈以太坊2.0:展望未来5到10年发展路线,以太坊将优于比特币
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First HTML and CSS
Elisabeth Robson、Eric Freeman / O'Reilly Media / 2012-9-8 / USD 39.99
Tired of reading HTML books that only make sense after you're an expert? Then it's about time you picked up Head First HTML and really learned HTML. You want to learn HTML so you can finally create th......一起来看看 《Head First HTML and CSS》 这本书的介绍吧!