组件化—依赖注入和服务定位的实践(一)

一、使用背景

随着Android组件化的不断深入,各个模块之间相互分离,尽可能的做到各个module内部强内聚,并且降低对外的耦合度。

IOC(Inversion of Control)原则在这个过程中发挥着至关重要的作用,控制权掌握在自己的module内,将我们需要的功能接口对外暴露,让其他业务线实现。

但现在问题来了,
           1、业务线实现暴露的接口后,在什么时机传递给拥有控制权的module中呢?
           2、如果这个有控制权的module不是被实现了接口的module所调用启动的,又该如何传递呢?

举个例子:
           假设以登录为例,在SDK中有登录界面及一个默认的登录逻辑类。现在有个新的业务线成立了,需要接入原来的登录模块,要求是界面不变,但是网络请求和登录逻辑需要变化,如果登录封装成为一个独立模块要如何做呢?

二、单纯实现功能的代码设计

定义一个接口,业务端实现后,在当前类中初始化,此时需要有业务端实现类的引用。

在LoginActivity中必须知道要用的是哪个真正的ILoginController实现类。

主要代码:

LoginActivity:

ILoginController loginController;


private void initController() {
    loginController = new CommonLoginController(this);
}

ILoginController:

public interface ILoginController {
    void login(String account, String password);
}

         CommonLoginController:

public class CommonLoginController implements ILoginController {
    LoginActivity activity;

    public CommonLoginController(LoginActivity activity) {
        this.activity = activity;
    }

    @Override
    public void login(String account, String password) {
        if ("111".equals(account) && "222".equals(password)){
            activity.onLoginSuccessed();
        } else {
            activity.onLoginFailed();
        }
    }
}

         该方式虽然实现了功能的解耦,但是Activity需要在编写时就要知道真正要的实现类是哪一个IController,如果场景是IController的实现类需要写到外部非引用module中,这个需求如何实现呢?

三、直接反应及逐步优化

 1、通过反射获取到实现类。
             问题:(1)需要充分沟通,在开发前两端人员需要确定开放的接口和初始化的方式等
                        (2)因为是反射调用,代码可读性较差(尤其在实现类发现有无用方法时,也不敢删除)
                        (3)如果多个客户端需要用使用该逻辑,每个客户端都需要进行一次沟通及开发

实现方式:

ILoginController没有任何变化

LoginActivity:

ILoginController loginController;
private void initController() {
        try {
            //反射登录逻辑
            Class clazz = Class.forName("com.alzzz.dilogindemo.impl.InvokeLoginController");
            Constructor<ILoginController> constructor = clazz.getConstructor(new Class[]{Context.class});
            loginController = constructor.newInstance(this);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

在app包中的InvokeLoginController:

public class InvokeLoginController implements ILoginController {
    private Context context;

    public InvokeLoginController(Context context) {
        this.context = context;
    }

    @Override
    public void login(String account, String password) {
        if ("333".equalsIgnoreCase(account) 
                && "444".equalsIgnoreCase(password)){
            Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show();
        }
    }
}

该方式实现了SDK包在运行时调用App包中方法的功能,从UML图中可以发现,此时的InvokeLoginController与LoginActivity是没有依赖关系的,这样的设计是符合IoC原则的,但是由于事先必须知道类名等信息,沟通成本和调试接入成本都比较高,那么就需要再进一步,SDK对外提供注册表,实现动态匹配。

           2、外部注册方案(服务定位)

实现目的:

(1)外部实现类的类名不再需要通过提前写死,可以动态进行绑定
           (2)真正的实现方法不再要求根据接口写死,改为用注解的方式进行匹配

还做不到的一件很重要事:初始化方法的参数仍要根据SDK的规定参数来定义。

实现方式:

简述:

1、SDK内部需要一个Register注册类,用于外部进行实现类的注册,demo中是LoginRegister类
           2、业务线需要实现对应的服务并且注册到Register中,demo中是LoginService类
           3、注册时机要早于调用时机

需要说明一下的是:下面demo中的设计思想是为了减少SDK与业务线的沟通成本,强制业务线必须返回ILoginController类型的对象以用于SDK进行回调操作,但这不是必须的,只是个人的理解,其实如果定义好了action和对应业务参数,是没有必须要非得规定返回值的。各有利弊。
            

Package app:

LoginService:服务定位器

@LoginController
public class LoginService {
    private Context mContext;
    public LoginService(Context mContext) {
        this.mContext = mContext;
    }

    @Action(action = "doLogin")
    public ILoginController outterDoLogin(){
        return new SLLoginController(mContext);
    }
}

SLLoginController:真正的处理类

public class SLLoginController implements ILoginController {
    private Context mContext;

    public SLLoginController(Context mContext) {
        this.mContext = mContext;
    }

    @Override
    public boolean login(String account, String password) {
        if ("555".equalsIgnoreCase(account) && "666".equalsIgnoreCase(password)){
            return true;
        }
        return false;
    }
}

MainActivity:服务绑定

LoginRegister.bind(LoginService.class);

Package SDK:

Annotation:用于标记

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface Action {
    String action();
}


/**
 * @Description 没有真正实际的意义,只是用于标记
 *
 * @Date 2019-06-13
 * @Author sz
 */
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
public @interface LoginController {
}

LoginRegister:注册器,根据Action获取到真正的实现方法

    private static Class targetController;
    private static Map<String, SoftReference<Method>> sCachedAction = new HashMap<>();

    public static void bind(@LoginController Class clazz) {
        targetController = clazz;
    }

    @Nullable
    public static ILoginController executeTargetAction(Context mContext, String action) {
        try {
            //根据action找到对应的方法
            Class clazz = targetController;
            if (clazz == null) {
                return null;
            }

            Constructor<ILoginController> constructor =
                    targetController.getConstructor(new Class[]{Context.class});
            Object obj = constructor.newInstance(mContext);

            Method actionMethod = getCachedMethod(action, clazz);
            if (actionMethod != null) {
                actionMethod.setAccessible(true);
                ILoginController iLoginController = (ILoginController) actionMethod.invoke(obj);
                return iLoginController;
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

SLLoginActivity:调用登录逻辑

            ILoginController iLoginController = LoginRegister.executeTargetAction(this, "doLogin");
            if (iLoginController == null){
                Log.d(TAG, "外部没有注册相关处理逻辑");
                return;
            }
            if (iLoginController.login(accountEt.getText().toString(),
                    passwordEt.getText().toString())){
                onLoginSuccessed();
            } else {
                onLoginFailed();
            }

到此时基本的功能需求已经完成,当然在真正工程中,还需要很重要的一步就是做一下default方案,即在外部没有或者有些action是强制必须走sdk内部的时候,需要直接调用内部的预留逻辑。

思考

1、在之前就提到这个方案有一件做不到的事,就是类的初始化是必须制定死的,要么就是初始化参数固定、要么直接方法是静态方法不需要初始化。有没有可能再进一步优化此方案,让初始化也变成动态的呢?

2、每次我们在调用方法的时候都需要去初始化一下这个类对象,在有些需求里面,这个LoginService的对象其实完全可以是单例或者是在某个作用域下是单例的,有没有可能在SDK中实现呢?

3、如果实现类现在还要SDK中的其他依赖又该如何处理呢?

答案很明显都是可以做到的,计划在下一篇依赖注入博客中实现...

代码地址:https://github.com/Alzzzz/DILoginDemo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值