精华内容
下载资源
问答
  • 只好重新添加一个发送模块 ,这样就相当于维护了两套代码,发送功能和发送格式配置代码耦合太严重,一直想着重构这个功能,后来有时间了,开始考虑重构了。一直在想我的发送功能是固定的,如何把发送的...

    1、反射使用的背景

    最近在做一个功能,就是实现邮件发送功能,但是邮件发送有不同的内容和数据格式,在开始设计的时候直接将发送的内容写在了发送模块中,后来发现功能增加后,无法继续在里边写了,因为里边的功能已经固定住了,只好重新添加一个发送模块 ,这样就相当于维护了两套代码,发送功能和发送格式配置代码耦合太严重,一直想着重构这个功能,后来有时间了,开始考虑重构了。

    一直在想我的发送功能是固定的,如何把发送的不同内容和格式抽取取来呢,以后添加新内容,只需把样式和格式的类写好,发送模块会自动匹配要发送的内容呢;一开始想到用多态,父类去调用子类的功能,但是发现一个问题,就是每次调用的时候需要通过 Fu f = new Zi() 这种模式进行创建对象,但是在发送模块不能确定使用哪个子类去创建,在不改变代码的情况下无法做到new Zi()的动态化;

    后来相当了,如果我每次发送不同功能的时候,可以读取配置文件来确定使用哪个类进行调用,然后发送这个类的内容和格式,这时候突然想到了使用反射技术,在发送模块我写成反射模式,反射的时候调用的通过读取配置文件来确定所要调用的类和方法,每次添加了新功能,我只要设置配置文件,那么反射的代码可以更具配置去使用该类,然后调用其方法,完全做到了发送模块与内容格式的分离,不需要再去维护发送模块了。nice!!!

    反射技术是实现各大框架的重要技术之一!

    2、过程描述

    109422cce560770d09578085b8a39104.png

    从图中看出 反射地方可以根据配置文件自动的实现调用不同的功能,所以说 以后当我们新增功能的时候,我们只需要写好对应的类以及对应配置文件,那么就会自动调用新增代码了;

    3、反射技术的原理

    dcb4560c08a9c2af0981c2f5d0a0f6fa.png

    3.1、获取class的方式 通用方式

    Class car = Class.forName("com.gxy.src.Car");   //注意此字符串必须是真实路径,就是带包名的类路径,包名.类名

    3.2、获取class的构造函数

    1.获取构造方法:

    1).批量的方法:

    public Constructor[] getConstructors():所有"公有的"构造方法

    public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

    Constructor[] con = car .getDeclaredConstructors();

    2).获取单个的方法,并调用:

    public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:

    public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;

    Constructor con = clazz.getConstructor(null);

    Constructor con = clazz.getConstructor(Char.class);

    调用构造方法:

    Constructor-->newInstance(Object... initargs)

    Object obj = con.newInstance();

    Object obj = con.newInstance('r');

    2、newInstance是 Constructor类的方法(管理构造函数的类)

    api的解释为:

    newInstance(Object... initargs)

    使用此

    Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。

    它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用

    3.3、获取class的方法 通过代码来看

    //获取类中所有的方法。

    public static void method_1() throwsException {

    Class clazz= Class.forName("com.makaruina.reflect.Person");//获取的是该类中所有的公有方法,包含继承和实现的方法。

    Method[] methods = clazz.getMethods();//获取的是该类中的所有方法,包含私有方法,但不包含继承的方法。

    methods =clazz.getDeclaredMethods();for(Method method : methods) {

    System.out.println(method);

    }

    }//获取指定方法;

    public static void method_2() throwsException {

    Class clazz= Class.forName("com.makaruina.reflect.Person");//获取指定名称的方法。

    Method method = clazz.getMethod("show", int.class,String.class);//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。

    Object obj =clazz.newInstance();

    method.invoke(obj,39,"hehehe");//执行一个方法

    }//想要运行私有方法。

    public static void method_3() throwsException {

    Class clazz= Class.forName("com.makaruina.reflect.Person");//想要获取私有方法。必须用getDeclearMethod();

    Method method = clazz.getDeclaredMethod("method", null);//私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。

    method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。

    }//反射静态方法。

    public static void method_4() throwsException {

    Class clazz= Class.forName("com.makaruina.reflect.Person");

    Method method= clazz.getMethod("function",null);

    method.invoke(null,null);

    }

    3.3、获取class的属性 通过代码来看

    *获取成员变量并调用:*

    * 1.批量的* 1).Field[] getFields():获取所有的"公有字段"

    * 2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;* 2.获取单个的:* 1).public Field getField(String fieldName):获取某个"公有的"字段;* 2).publicField getDeclaredField(String fieldName):获取某个字段(可以是私有的)*

    *设置字段的值:* Field --> public voidset(Object obj,Object value):*参数说明:* 1.obj:要设置的字段所在的对象;* 2.value:要为字段设置的值;

    3.4、获取main方法 通过代码来看

    try{//1、获取Student对象的字节码

    Class clazz = Class.forName("fanshe.main.Student");//2、获取main方法

    Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,//3、调用main方法//methodMain.invoke(null, new String[]{"a","b","c"});//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数//这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。

    methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一//methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二

    }catch(Exception e) {

    e.printStackTrace();

    }

    4、通过反射可以越过泛型检查

    为什么泛型可以越过泛型检查  ---  泛型用在编译期,编译过后泛型擦除(自动失效)。

    举例:

    ArrayList strList = new ArrayList<>();

    strList.add("aaa");

    strList.add("bbb");//strList.add(100);//获取ArrayList的Class对象,反向的调用add()方法,添加数据

    Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象//获取add()方法

    Method m = listClass.getMethod("add", Object.class);//调用add()方法

    m.invoke(strList, 100);//遍历集合

    for(Object obj : strList){

    System.out.println(obj);

    }

    展开全文
  • 问题是探讨耦合双摆中改变不同的参量对于结果的影响,本文给出的是理论分析的部分,下一篇会给出通过实验数据进行分析的部分。我实验的时候是用Matlab来构建的初始代码,再迁移到Python中,因此Matlab部分难免有些...

    最近做的一个 coursework 拿来跟大家分享一下。

    问题是探讨耦合双摆中改变不同的参量对于结果的影响,本文给出的是理论分析的部分,下一篇会给出通过实验数据进行分析的部分。我实验的时候是用Matlab来构建的初始代码,再迁移到Python中,因此Matlab部分难免有些简陋。

    耦合双摆如下图所示:

    807aa75fef4342b1823f01fed24d6e05.png
    Figure 1

    图中的

    是可调节的参数。 最上方的两点是固定点,用来固定两个摆臂的,摆臂的下方分别固定住两部手机作为传感器用。 手机的外形参数在图中为
    是固定点到手机形心的距离。
    是两个摆臂的初始角度。k是弹簧的系数。 w则是两个固定点的距离。

    这里主要用到的知识是理论力学。

    1 : 画出耦合双摆的简图,并建立合适的坐标系

    b810ee91aafd4d8bdfb2f1260f39a395.png
    Figure 2

    2 : 受力分析, 如Figure 3 所示

    c17dcb7b6efea0200bf0ce0a789a37c6.png
    Figure 3

    3 : 整理下

    根据 Figure 2 的坐标系和 Figure 3 的 受力分析图, 我们可得如下式子

    (这里写成向量式是为了在后面使用角动量守恒定律)

    4 : 分析几何关系

    我们已经得到了受力情况, 我们的目标是得到运动的方程, 因此要用到角动量守恒。

    来张结构简图

    e2bbb4babafc2d77290046a6d337b203.png
    Figure 4

    根据几何关系得到下式

    整理上面二式可得:

    得到 c 和 d 后我们根据勾股定理可得弹簧的当前长度及两个角度:

    对于

    我们有:

    整理可得

    5 : 角动量守恒

    展开上式:

    继续展开:

    由转动惯量公式可得:

    联立:

    我们得到了角加速度的等式,目的已经达成,现在就是用 Matlab 和 Python 去求解这个二阶耦合微分方程。


    首先上述的问题是一个初值问题。因此我们要确定上述式中的某些常量值,角度的初值和角速度的初值。

    c759bc886cb4e8ff77b838f7f7e753b9.png
    Figure 5

    上图给的是作者在实验中用到的参量值

    对于一个普通的二阶微分方程():

    如果要在Matlab中用数值法求解,我们需要将其改写为如下形式

    因此对于上式,我们只需要写出如下的结构:

    [t,y] = ode23(@vdp1,[0 20],[2; 0]);
    % mu 取 1
    function dydt = vdp1(t,y)
    dydt = [y(2); (1-y(1)^2)*y(2)-y(1)];
    end

    其中对于 ode23, 最为朴素的写法是:

    [t,y] = ode23(odefun,tspan,y0)

    代表时间区间,
    代表初始条件,这里的2即为开始时
    的值, 0代表开始时
    的值。详细的写法参考Matlab官方文档:
    Solve nonstiff differential equationsuk.mathworks.com
    6845d30ae47a9c16fa6760f0dadd6a9e.png

    从上可得,对于一个二阶微分方程,我们需要构造出两个一阶微分方程。 对于我们的耦合双摆, 我们要求解的目标为二阶耦合微分方程, 因此我们需要构造出四个一阶微分方程。

    程序如下:

    %清理 workspace
    clc,clear
    close all
    
    % 初值[角1, 角速度1, 角2, 角速度2]
    y0 = [20*pi/180;0;20*pi/180;0];
    
    % 求解ode
    [t,y] = ode23s(@vdp1,[0,15],y0);
    
    
    % 画图
    figure
    subplot(1,2,1)
    hold on
    plot(t,y(:,1),'LineWidth',2);
    plot(t,y(:,3),'LineWidth',2);
    xlabel('Time (sec)','FontSize',25)
    ylabel('Acceleration (m/s^2)','FontSize',25)
    legend({'M1','M2'},'FontSize',25)
    
    subplot(1,2,2)
    plot(y(:,1),y(:,3),'LineWidth',2);
    xlabel('Angle (deg)','FontSize',25)
    ylabel('Angle (deg)','FontSize',25)
    legend({'M1','M2'},'FontSize',25)
    
    
    
    % ode 方程
    function dydt = vdp1(t,th)
    
    % 参量
    m1 = 0.174;
    m2 = 0.174;
    l1 = 0.38;
    l2 = 0.38;
    a1 = 0.30;
    a2 = 0.30;
    k = 1.4526;
    w = 0.35;
    H = 0.1436;
    W = 0.0709;
    g = 9.8;
    x0 = 0.25;
    
    % 惯性矩
    Izz1 = (1/12) * m1 * (H^2 + W^2) + m1 * l1^2;
    Izz2 = (1/12) * m2 * (H^2 + W^2) + m2 * l2^2;
    
    % 几何关系式
    c = w + a1 * sin(th(1)) - a2 * sin(th(3));
    d = 0 + a1 * cos(th(1)) - a2 * cos(th(3));
    
    % 弹簧长度
    x = sqrt(c^2 + d^2)-x0;
    
    % 切向角
    alpha1 = atan2(d,c) + th(1);
    alpha2 = atan2(d,c) + th(3);
    
    
    dydt = [th(2);
        (-1/Izz1) * (a1 * k * x * cos(alpha1) + m1 * g * l1 * sin(th(1)));
        th(4);
        (+1/Izz2) * (a2 * k * x * cos(alpha2) - m2 * g * l2 * sin(th(3)))];
    end

    结果如下图所示:

    b7abbd5aa36a5485e288a8bc63050ff3.png
    直接截图的,大家最好还是用save指令

    最后贴上我的Python代码供大家参考(写的稀烂,轻喷):

    # Import necseeary lib
    import numpy as np
    import matplotlib.pyplot as plt
    from math import sin,cos,atan2,radians
    from scipy.integrate import solve_ivp
    
    # Define function for solving differential equation 
    # which derive from coupled pendulum
    def pen_1(Z,const):
        
        
        # constant
        m1, m2, l1, l2, a1, a2, k, w, H, W, g, x0 = const
        # The first angle
        th1 = Z[0]
        # The second angle
        th2 = Z[2]
        # The first angle velocity
        dth1 = Z[1]
        # The second angle velocity
        dth2 = Z[3]
        
        
        # The inertia of phone 1
        Izz1 = (1/12) * m1 * (H**2 + W**2) + m1 * l1**2
        # The inertia of phone 2 
        Izz2 = (1/12) * m2 * (H**2 + W**2) + m2 * l2**2
        
        
        # Geometric relationship
        c = w + a1 * sin(th1) - a2 * sin(th2)
        d = 0 + a1 * cos(th1) - a2 * cos(th2)
        x = (c**2 + d**2)**0.5 - x0
        
        
        # Determine if the spring will be compressed
        if x<0:
            print('The springi is getting shorter!')
        
        
        # Geometric relationship
        alpha1 = atan2(d,c) + th1
        alpha2 = atan2(d,c) + th2
        
        
        # Damping factor
        c1 = 0.000
        c2 = c1
        
        
        # Angle acceleration of the phone 1
        ddth1 = (-1/Izz1) * (a1 * k * x * cos(alpha1) + m1 * g * l1 * sin(th1))-c1/Izz1 * dth1
        # Angle acceleration of the phone 2
        ddth2 = (+1/Izz2) * (a2 * k * x * cos(alpha2) - m2 * g * l2 * sin(th2))-c2/Izz1 * dth2
        
        Zd = [dth1,ddth1,dth2,ddth2]
        return Zd
    
    # Time of three experiments 
    t_w = [81.6, 52.2, 69.0]
    
    # Width of three experiments
    # For graph title
    WW = [35, 32.5, 30]
    
    # Width of three experiments
    width = [0.35, 0.325, 0.30]
    
    # Experiment index
    # For graph title
    index = [1, 6, 7]
    
    ## Constant
    m1 = 0.174  # kg
    m2 = 0.174  # kg
    l1 = 0.38   # m
    l2 = 0.38   # m
    a1 = 0.30   # m
    a2 = 0.30   # m
    # Spring factor
    k = 1.4526  # N/m
    # Height of phone
    H = 0.1436  # m
    # Weight of phone
    W = 0.0709  # m
    g = 9.8     # m/s^2
    # Initial spring length
    x0 = 0.25
    
    
    # Used for font in charts
    csfont = {'fontname':'Times New Roman'}
    # Font size
    fs = 22
    
    
    # Cycle three times
    # I made three expeiments with different parameters
    #for ii in range(0,3):
    ii = 0
    # Load every experimental parameters
    w = width[ii]
    t_end = t_w[ii]
    constant = m1, m2, l1, l2, a1, a2, k, w, H, W, g,x0
    
    # Create a new time series, time interval set as 0.1 sec
    t = np.linspace(0,t_end,t_end*10+1)
    
    # set initial value
    Z0 = [radians(20),0,radians(20),0]
    
    # solve coupled pendulum equation
    sol = solve_ivp(lambda t,Z: pen_1(Z,constant),[0, t_end],Z0, t_eval = t, rtol = 1e-6)
    
    # Store result in 'data' varaible
    data = sol.y
    
    # Get the data length
    size = np.size(data,1)
    
    # Create a list with four elements
    acce = [0,0,0,0]
    
    # Iteriate four times 
    # Change each element to nparray
    # Each nparry is the same length as the data
    for iii in range(0,4):
        acce[iii] = np.zeros(np.size(data,1)*2).reshape(2,np.size(data,1))
    
    # Iteriate 'size' times
    for iii in range(0,size):
        
        # Load the first angle and tge second angle to the th1 and th2 varaibles
        th1 = data[0][iii]
        th2 = data[2][iii]
        
        # Assign inertia to Izz1 and Izz2 varaibles
        Izz1 = (1/12) * m1 * (H**2 + W**2) + m1 * l1**2
        Izz2 = (1/12) * m2 * (H**2 + W**2) + m2 * l2**2
        
        # Geometric relationship
        c = w + a1 * sin(th1) - a2 * sin(th2)
        d = 0 + a1 * cos(th1) - a2 * cos(th2)
        
        # Deformation length
        x = (c**2 + d**2)**0.5 - x0
        
        # Derive the angle 1 and angle 2
        alpha1 = atan2(d,c) + th1
        alpha2 = atan2(d,c) + th2
        
        # Damping factors
        c1 = 0.000
        c2 = c1
        
        
        # For acce lsit
        # The first number are one, two, three and four, which indicates angle acceleration, 
        # tangential acceleration, centripetal acceleration and combined acceleration respectively
        # The second number indicates different phones
        # The third number inidcates list index
        
        # Assign the angle acceleration to the first element of acce
        acce[0][0][iii] = (-1/Izz1) * (a1 * k * x * cos(alpha1) + m1 * g * l1 * sin(th1)) - c1/Izz1 * data[1][iii]
        acce[0][1][iii] = (+1/Izz2) * (a2 * k * x * cos(alpha2) - m2 * g * l2 * sin(th2)) - c2/Izz2 * data[3][iii]
        
        # Assign the tangential acceleration to the second element of acce
        # Tangential acceleration = Angle acceleration * radius
        acce[1][0][iii] = acce[0][0][iii] * l1
        acce[1][1][iii] = acce[0][1][iii] * l2
        
        # Assign the centripetal acceleration to the third element of acce
        # Centripetal acceleration = (angle velocity)^2 * radius
        acce[2][0][iii] = data[1][iii]**2 * l1
        acce[2][1][iii] = data[3][iii]**2 * l2
        
        # Assign the combined acceleration to the last element of acce
        acce[3][0][iii] = (acce[1][0][iii]**2 + acce[2][0][iii]**2)**0.5
        acce[3][1][iii] = (acce[1][1][iii]**2 + acce[2][1][iii]**2)**0.5
    
    # Calculate the average acceleration of each phone
    mean1 = sum(acce[3][0])/size
    mean2 = sum(acce[3][1])/size
    print('mean acc of m1: ',mean1, 
          'nmean acc of m2:',mean2,'n')
    
    # Create the corrisponding time series for plotting
    t_seq = np.linspace(0,t_w[ii],t_w[ii]*10+1)
    
    
    # Set the figure size
    plt.figure(figsize = (16,16))
    
    # Plot Combined acceleration graph
    plt.subplot(1,2,1)
    plt.plot(t_seq[0:200],acce[3][0][0:200],label = 'M1')
    plt.plot(t_seq[0:200],acce[3][1][0:200],label = 'M2')
    
    
    # Set xlable, ylabel and legend 
    plt.xlabel('Time (sec)',**csfont,fontsize=fs)
    plt.ylabel('Combined acceleration (m/s^2)',**csfont,fontsize=fs)
    plt.legend(loc=1, prop={'size': fs})
    
    
    # Set title
    title = 'Simulation (W = ' + str(WW[ii]) + 'cm' + ')'
    plt.title(title,**csfont,fontsize=fs)
    plt.tight_layout()
    plt.grid()
    
    # Plot Tangential acceleration graph
    plt.subplot(1,2,2)
    plt.plot(t_seq[0:200],acce[1][0][0:200],label = 'M1')
    plt.plot(t_seq[0:200],acce[1][1][0:200],label = 'M2')
    
    
    # Set xlable, ylabel and legend 
    plt.xlabel('Time (sec)',**csfont,fontsize=fs)
    plt.ylabel('Tangential acceleration (m/s^2)',**csfont,fontsize=fs)
    
    
    # Set title
    title = 'Simulation (W = ' + str(WW[ii]) + 'cm' + ')'
    plt.title(title,**csfont,fontsize=fs)
    plt.tight_layout()
    plt.grid()
    展开全文
  • jquery使用attr访问自定义属性,减少javascript脚本中代码数据耦合
                   

       jquery使用attr访问自定义属性,减少javascript脚本中代码和数据的耦合

    [示例代码]

    <html>
        <head>
            <script src="jquery-1.2.js"></script>
            <script>
            $(document).ready (
                function () {
                    $("#link").click (
                        function () {
                            alert($(this).attr("var"));
                        }
                    );
                }
            );
            </script>
        </head>
        <body>
            <a id="link" var="value" href="">
                使用attr访问自定义属性,减少javascript脚本中代码和数据的耦合
            </a>
        </body>
    </html>

    [jQuery官方网站]

    http://jquery.com/

       [jQuery下载地址]

    http://docs.jquery.com/Downloading_jQuery

       [jQuery当前版本]

    http://docs.jquery.com/Release:jQuery_1.2

       [jQuery相关论坛]

    http://groups.google.com/group/jquery-en

       [jQuery关键词]

    jQuery

       [jQuery特效]

       jQuery淡入特效,http://docs.jquery.com/Effects/fadeIn

       [jQuery选择器]

    表单元素选择器, http://docs.jquery.com/DOM/Traversing/Selectors#Form_Selectors

       [jQuery常用函数]

       ajax, http://docs.jquery.com/Ajax/jQuery.ajax
    attr访问自定义属性,减少javascript脚本中代码和数据的耦合
    click, 绑定元素的onclick事件,http://docs.jquery.com/Events/click 
    filter, 用于在集合中过滤元素
    hover, 用于设置元素响应mouseover和mouseleave

       [jQuery插件]

    Easing,http://jquery.com/plugins/project/easing

       [jQuery重要工具]

    http://www.json.org/, http://www.json.org/json.js           

    再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow

    展开全文
  • 减少前端代码耦合

    2019-01-24 14:55:00
    什么是代码耦合代码耦合的表现是改了一点毛发而牵动了全身,...全局耦合就是几个类、模块共用了全局变量或者全局数据结构,特别是一个变量跨了几个文件。例如下面,在html里面定义了一个变量: <script>...

    什么是代码耦合?代码耦合的表现是改了一点毛发而牵动了全身,或者是想要改点东西,需要在一堆代码里面找半天。由于前端需要组织js/css/html,耦合的问题可能会更加明显,下面按照耦合的情况分别说明:

    避免全局耦合

    这应该是比较常见的耦合。全局耦合就是几个类、模块共用了全局变量或者全局数据结构,特别是一个变量跨了几个文件。例如下面,在html里面定义了一个变量:

    <script>
        var PAGE = 20;
    </script>
     
    <script src="main.js"></script>
    

    上面在head标签里面定义了一个PAGE的全局变量,然后在main.js里面使用。这样子PAGE就是一个全局变量,并且跨了两个文件,一个html,一个js。然后在main.js里面突然冒出来了个PAGE的变量,后续维护这个代码的人看到这个变量到处找不到它的定义,最后找了半天发现原来是在xxx.html的head标签里面定义了。这样就有点egg pain了,并且这样的变量容易和本地的变量发生命名冲突。

    所以如果需要把数据写在页面上的话,一个改进的办法是在页面写一个form,数据写成form里面的控件数据,如下:

    <form id="page-data">
        <input type="hidden" name="page" value="2">
        <textarea name="list" style="display:none">[{"userName": ""yin"},{}]</textarea>
    </form>
    

    上面使用了input和textarea,使用textarea的优点是支持特殊符号。再把form的数据序列化,序列化也是比较简单的,可以查看Effective前端2:优化html标签

    第二种是全局数据结构,这种可能会使用模块化的方法,如下:

    //data.js
    module.exports = {
        houseList: null
    }
     
    //search.js 获取houseList的数据
    var data = require("data");
    data.houseList = ajax();
    require("format-data").format();
     
    //format-data.js 对houseList的数据做格式化
    function format(){
        var data = require("data");
        process(data);
        require("show-result").show();
    }
     
    //show-result.js 将数据显示出来
    function show(){
        showData(require("data").houseList)
    }
    

    上面四个模块各司其职,乍一眼看上去好像没什么问题,但是他们都用了一个data的模块共用数据。这样确实很方便,但是这样就全局耦合了。因为用的同一个data,所以你无法保证,其它人也会加载了这个模块然后做了些修改,或者是在你的某一个业务的异步回调也改了这个。第二个问题:你不知道这个data是从哪里来的,谁可能会对它做了修改,这个过程对于后续的模块来说都是不透明的。

    所以这种应该考虑使用传参的方式,降低耦合度,把data作为一个参数传递:

    //去掉data.js
    //search.js 获取数据并传递给下一个模块
    var houseList = ajax();
    require("format-data").format(houseList);
     
    //format-data.js 对houseList的数据做格式化
    function format(houseList){
        process(houseList);
        require("show-result").show(houseList);
    }
     
    //show-result.js 将数据显示出来
    function show(houseList){
        showData(houseList)
    }
    

    可以看到,search里面获取到data后,交给format-data处理,format-data处理完之后再给show-result。这样子就很清楚地知道数据的处理流程,并且保证了houseList不会被某个异步回调不小心改了。如果单独从某个模块来说,show-result这个模块并不需要关心houseList的经过了哪些流程和处理,它只需要关心输入是符合它的格式要求的就可以。

    这个时候你可能会有一个问题:这个data被逐层传递了这么多次,还不如像最上面的那样写一个data的模块,大家都去改那里,岂不是简单了很多?对,这样是简单了,但是一个数据结构被跨了几个文件使用,这样会出现我上面说的问题。有时候可能出现一些意想不到的情况,到时候可能得找bug找个半天。所以这种解耦是值得的,除非你定义的变量并不会跨文件,它的作用域只在它所在的文件,这样会好很多。或者是data是常量的,data里面的数据定义好之后值就再也不会改变,这样应当也是可取的。

    js/css/html的耦合

    这种耦合在前端里面应该最常见,因为这三者通常具有交集,需要使用js控制样式和html结构。如果使用js控制样式,很多人都喜欢在js里面写样式,例如当页面滑动到某个地方之后要把某个条吸顶:

     

    页面滑到下面那个灰色的条再继续往下滑的时候,那个灰色条就要保持吸顶状态:

    可能不少人会这么写:

    $(".bar").css({
        position: fixed;
        top: 0;
        left: 0;
    });
    

    然后当用户往上滑的时候取消fixed:

    $(".bar").css({
        position: static;
    });
    

    如果你用react,你可能会设置一个style的state数据,但其实这都一样,都把css杂合到js里面了。某个想要检查你样式的人,想要给你改个bug,他检查浏览器发现有个标签style里的属性,然后他找半天找不到是在哪里设置的,最后他发现是在某个js的某个隐蔽的角落设置了。你在js里面设置了样式,然后css里面也会有样式,在改css的时候,如果不知道js里面也有设置了样式,那么可能会发生冲突,在某种条件下触发了js里面设置样式。

    所以不推荐直接在js里面更改样式属性,而应该通过增删类来控制样式,这样子样式还是回归到css文件里面。例如上面可以改成这样:

    //增加fixed
    $(".bar").addClass("fixed");
     
    //取消fixed
    $(".bar").removeClass("fixed");
    

    fixed的样式:

    .bar.fixed{
        position: fixed;
        left: 0;
        top: 0;
    }
    

    可以看到,这样的逻辑就非常清晰,并且回滚fixed,不需要把它的position还原为static,因为它不一定是static,也有可能是relative,这种方式在取消掉一个类的时候,不需要去关心原本是什么,该是什么就会是什么。

    但是有一种是避免不了的,就是监听scroll事件或者mousemove事件,动态地改变位置。

    这种通过控制类的方式还有一个好处,就是当你给容器动态地增删一个类时,你可以借助子元素选择器,用这个类控制它的子元素的样式,也是很方便。

    还有很多人可能会觉得html和css/js脱耦,那就是不能在html里面写style,不能在html里面写script标签,但是凡事都不是绝对的,如果有一个标签,它和其它标签就一个font-size不一样,那你直接给它写一个font-size的内联样式,又何尝不可呢,在性能上来说,如果你写个class,它还得去匹配这个class,比不上style高效吧。或者是你这个html文件就那么20、30行css,那直接在head标签加个style,直接写在head里面好了,这样你就少管理了一个文件,并且浏览器不用去加载一个外链的文件。

    有时候直接在html写script标签是必要的,它的优势也是不用加载外链文件,处理速度会很快,几乎和dom渲染同时,这个在解决页面闪动的时候比较有用。因为如果要用js动态地改变已经加载好的dom,放在外链里面肯定会闪一下,而直接写的script就不会有这个问题,即使这个script是放在了body的后面。例如下面:

    原始数据是带p标签的,但是在textarea里面展示的时候需要把p改成换行\r\n,如果在dom渲染之后再在外链里面更新dom就会出现上面的闪动的情况。你可能会说我用react,数据都是动态渲染的,渲染前已经处理好了,不会出现上面的情况。那么,好吧,至少你了解一下吧。

    和耦合相对的是内聚,写代码的原则就是低耦合、高聚合。所谓内聚就是说一个模块的职责功能十分紧密,不可分割,这个模块就是高内聚的。我们先从重复代码说起:

    减少重复代码

    假设有一段代码在另外一个地方也要被用到,但又不太一样,那么最简单的方法当然是copy一下,然后改一改。这也是不少人采取的办法,这样就导致了:如果以后要改一个相同的地方就得同时改好多个地方,就很麻烦了。

    例如有一个搜索的界面:

     

    用户可以通过点击search按钮触发搜索,也可以通过点击下拉或者通过输入框的change触发搜索,所以你可能会这么写:

    $("#search").on("click", function(){
        var formData = getFormData();
        $.ajax({
            url: '/search',
            data: formData,
            success: function(data){
                showResult(data);
            }
        });
    });
    

    在change里面又重新发请求:

    $("input").on("change", function(){
        //把用户的搜索条件展示进行改变
        changeInputFilterShow();
        var formData = getFormData();
        $.ajax({
            url: '/search',
            data: formData,
            success: function(data){
                showResult(data);
            }
        });
    });
    

    change里面需要对搜索条件的展示进行更改,和click事件不太一样,所以图一时之快就把代码拷了一下。但是这样是不利于代码的维护的,所以你可能会想到把获取数据和发请求的那部分代码单独抽离封装在一个函数,然后两边都调一下:

    function getAndShowData(){
        var formData = getFormData();
        $.ajax({
            url: '/search',
            data: formData,
            success: function(data){
                showResult(data);
            }
        });
    }
     
    $("#search").on("click", getAndShowData);
    $("input").on("change", function(){
        changeInputFilterShow();
        getAndShowData();
    });
    

    在抽成一个函数的基础上,又发现这个函数其实有点大,因为这里面要获取表单数据,还要对数据进行格式化,用做请求的参数。如果用户触发得比较快,还要记录上次请求的xhr,在每次发请求前cancle掉上一次的xhr,并且可能对请求做一个loading效果,增加用户体验,还要对出错的情况进行处理,全部都要在ajax里面。所以最好对getAndShowData继续拆分,很自然地会想到把它分离成一个模块,一个单独的文件,叫做search-ajax。所有发请求的处理都在这个模块里面统一操作。对外只提供一个search.ajax的接口,传的参数为当前的页数即可。所有需要发请求的都调一下这个模块的这个接口就好了,除了上面的两种情况,还有点击分页的情景。这样不管哪种情景都很方便,我不需要关心请求是怎么发的,结果是怎么处理的,我只要传一个当前的页数给你就好了。

    再往下,会发现,在显示结果那里,即上面代码的第7行,需要对有结果、无结果的情况分别处理,所以又搞了一个函数叫做showResult,这个函数有点大,它里面的逻辑也比较复杂,有结果的时候除了更新列表结果,还要更新结果总数、更新分页的状态。因此这个showResult一个函数难以担当大任。所以把这个show-result也当独分离出一个模块,负责结果的处理。

    到此,我们整一个search的UML图应该是这样的:

    注意上面把发请求的又再单独封装成了一个模块,因为这个除了搜索发请求外,其它的请求也可以用到。同时search-result会用到两个展示的模板。

    由于不只一个页面会用到搜索的功能,所以再把上面继续抽象,把它封装成一个search-app的模块,需要用到的页面只需require这个search-app,调一下它的init函数,然后传些定制的参数就可以用了。这个search-app就相当于一个搜索的插件。

    所以整一个的思路是这样的:出现了重复代码 -> 封装成一个函数 -> 封装成一个模块 -> 封装成一个插件,抽象级别不断提高,将共有的特性和有差异的地方分离出来。当你走在抽象与封装的路上的时候,那你应该也是走在了大神的路上。

    当然,如果两个东西并没有共同点,但是你硬是要搞在一起,那是不可取的。

    我这里说的封装并不是说,你一定要使用requirejs、es6的import或者是webpack的require,关键在于你要有这种模块化的思想,并不是指工具上的,不管你用的哪一个,只要你有这种抽象的想法,那都是可取的。

    模块化的极端是拆分粒度太细,一个简单的功能,明明十行代码写在一起就可以搞定的事情,硬是写了七、八层函数栈,每个函数只有两、三行。这样除了把你的逻辑搞得太复杂之外,并没有太多的好处。当你出现了重复代码,或者是一个函数太大、功能太多,又或是逻辑里面写了三层循环又再嵌套了三层if,再或是你预感到你写的这个东西其他人也可能会用到,这个时候你才考虑模块化,进行拆分比较合适。

    上面不管是search-result还是search-ajax他们在功能上都是高度内聚的,每个模块都有自己的职责,不可拆分,这在面向对象编程里面叫做单一责职原则,一个模块只负责一个功能。

    再举一个例子,我在怎样实现前端裁剪上传图片功能里面提到一个上传裁剪的实现,这里面包含裁剪、压缩上传、进度条三大功能,所以我把它拆成三个模块:

    这里提到的模块大部分是一个单例的object,不会去实例它,一般可以满足大部分的需求。在这个单例的模块里面,它自己的“私有”函数一般是通过传参调用,但是如果需要传递的数据比较多的时候,就有点麻烦了,这个时候可以考虑把它封装成一个类。

    封装成一个类

    在上面的裁剪上传里面的进度条progress-bar,一个页面里可能有几个要上传的地方,每个上传的地方都会有进度条,每个进度条都有自己的数据,所以不能像在最上面说的,在一个文件的最上面定义一些变量然后为这个模块里面的函数共用,只能是通过传递参数的形式,即在最开始调用的时候定义一些数据,然后一层一层地传递下去。如果这些数据很多的话就有点麻烦。

    所以稍微变通一下,把progress-bar封装成一个类:

    function ProgressBar($container){
        this.$container = $container; //进度条外面的容器
        this.$meter = null;           //进度条可视部分
        this.$bar = null;             //进度条存放可视部分的容器
        this.$barFullWidth = $container.width() * 0.9; //进度条的宽度
        this.show();                  //new一个对象的时候就显示
    }
    

    或者你用ES6的class,但是本质上是一样的,然后这个ProgressBar的成员函数就可以使用定义的这些“私有”变量,例如设置进度条的进度函数:

    ProgressBar.prototype.setProgress = function(percentage, time){
        time = typeof time === "undefined" ? 100 : time;
        this.$meter.stop().animate({width: parseInt(this.$barFullWidth * percentage)}, time);
    };
    

    这个使用了两个私有变量,如果再加上原先两个,用传参的方式就得传四个。

    使用类是模块化的一种思想,另外一种常用的还有策略模式。

    使用策略模式

    假设要实现下面三个弹框:

    这三个弹框无论是在样式上还是在功能上都是一样的,唯一的区别是上面标题文案是不一样的。最简单的可能是把每个弹框的html都copy一下,然后改一改。如果你用react,你可能会用拆分组件的方式,上面一个组件,下面一个组件,那么好吧,你就这样搞吧。如果你没用react,你可能得想办法组织下你的代码。

    如果你有策略模式的思想,你可能会想到把上面的标题当作一个个的策略。首先定义不同弹框的类型,一一标志不同的弹框:

    var popType = ["register", "favHouse", "saveSearch"];
    

    定义三种popType一一对应上面的三个弹框,然后每种popType都有对应的文案:

    Data.text.pop = {
        register: {
            titlte: "Create Your Free Account",
            subTitle: "Search Homes and Exclusive Property Listings"
        },
        favHouse: {title: "xxx", subTitle: "xxx" },
        saveSearch: {title: "xxx", subTitle: "xxx"}
    };
    

    {tittle: “”, subtitle: “”}这个就当作是弹框文案策略,然后再写弹框的html模板的时候引入一个占位变量:

    <section>
        {{title}}
        {{subTitile}}
        <div>
            <!--其它内容-->
        </div>
    </section>
    

    在渲染这个弹框的时候,根据传进来的popType映射到不同的文案:

    function showPop(popType){
        Mustache.render(popTemplate, Data.text.pop[popType])
    }
    

    这里用Data.text.pop[popType]映射到了对应的文案,如果用react你把一个个的标题封装成一个组件,其实思想是一样的。

    但是这个并不是严格的策略模式,因为策略就是要有执行的东西嘛,我们这里其实是一个写死的文案,但是我们借助了策略模式的思想。接下来继续说使用策略模式做一些执行的事情。

    在上面的弹框的触发机制分别是:用户点击了注册、点击了收藏房源、点击了保存搜索条件。如果用户没有登陆就会弹一个注册框,当用户注册完之后,要继续执行用户原本的操作,例如该收藏还是收藏,所以必须要有一个注册后的回调,并且这个回调做的事情还不一样。

    当然,你可以在回调里面写很多的if else或者是case:

    function popCallback(popType){
        switch(popType){
            case "register": 
                //do nothing
                break;
            case: "favHouse": 
                favHouse();
                break;
            case: "saveSearch":
                saveSearch();
                break;
        }
    }
    

    但是当你的case很多的时候,看起来可能就不是特别好了,特别是if else的那种写法。这个时候就可以使用策略模式,每个回调都是一个策略:

     

    var popCallback = {
        favHouse: function(){
            //do sth.
        },
        saveSearch: function(){
            //do sth.
        }
    }
    

    然后根据popType映射调用相应的callback,如下:

    var popCallback = require("pop-callback");
    if(typeof popCallback[popType] === "function"){
        popCallback[popType]();
    }
    

    这样它就是一个完整的策略模式了,这样写有很多好处。如果以后需要增加一个弹框类型popType,那么只要在popCallback里面添加一个函数就好了,或者要删掉一个popType,相应地注释掉某个函数即可。并不需要去改动原有代码的逻辑,而采用if else的方式就得去修改原有代码的逻辑,所以这样对扩展是开放的,而对修改是封闭的,这就是面向对象编程里面的开闭原则。

    在js里面实现策略模式或者是其它设计模式都是很自然的方式,因为js里面function可以直接作为一个普通的变量,而在C++/Java里面需要用一些技巧,玩一些OO的把戏才能实现。例如上面的策略模式,在Java里面需要先写一个接口类,里面定义一个接口函数,然后每个策略都封装成一个类,分别实现接口类的接口函数。而在js里面的设计模式往往几行代码就写出来,这可能也是做为函数式编程的一个优点。

    前端和设计模式经常打交道的还有访问者模式

    访问者模式

    事件监听就是一个访问者模式,一个典型的访问者模式可以这么实现,首先定义一个Input的类,初始化它的访问者列表

    function Input(inputDOM){
        //用来存放访问者的数据结构
        this.visitiors = {
            "click": [],
            "change": [],
            "special": [] //自定义事件
        }
        this.inputDOM = inputDOM;
    }
    

    然后提供一个对外的添加访问者的接口:

    Input.prototype.on = function(eventType, callback){
        if(typeof this.visitiors[eventType] !== "undefined"){
            this.visitiors[eventType].push(callback);
        }
    };
    

    使用者调用on,传递两个参数, 一个是事件类型,即访问类型,另外一个是具体的访问者,这里是回调函数。Input就会将访问者添加到它的访问者列表。

    同时Input还提供了一个删除访问者的接口:

    Input.prototype.off = function(eventType, callback){
        var visitors = this.visitiors[eventType];
        if(typeof visitiors !== "undefined"){
            var index = visitiors.indexOf(callback);
            if(index >= 0){
                visitiors.splice(index, 1);
            }
        }
    };
    

    这样子,Input就和访问者建立起了关系,或者说访问者已经成功地向接收者都订阅了消息,一旦接收者收到了消息会向它的访问者一一传递:

    Input.prototype.trigger = function(eventType, event){
        var visitors = this.visitiors[eventType];
        var eventFormat = processEvent(event); //获取消息并做格式化
        if(typeof visitors !== "undefined"){
            for(var i = 0; i < visitors.length; i++){
                visitors[i](eventFormat);
            }
        }
    };
    

    trigger可能是用户调的,也可能是底层的控件调用的。在其它领域,它可能是一个光感控件触发的。不管怎样,一旦有人触发了trigger,接收者就会一一下发消息。

    如果你知道了事件监听的模式是这样的,可能对你写代码会有帮助。例如点击下面的搜索条件的X,要把上面的搜索框清空,同时还要触发搜索,并把输入框右边的X去掉。要附带着做几件事情。

     

    这个时候你可能会这样写:

    $(".icon-close").on("click", function(){
        $(this).parent().remove(); //删除本身的展示
        $("#search-input").val("");
        searchAjax.ajax();         //触发搜索
        $("#clear-search").hide(); //隐藏输入框x
    });
    

    但其实这样有点累赘,因为在上面的搜索输入框肯定也会相应的操作,当用户输入为空时,自动隐藏右边的x,并且输入框change的时候会自动搜索,也就是说所有附加的事情输入框那边已经有了,所以其实只需要触发下输入框的change事件就好了:

    $(".icon-close").on("click", function(){
        $(this).parent().remove(); //删除本身的展示
        $("#search-input").val("").trigger("change");
    });
    

    输入框为空时,该怎么处理,search输入框会相应地处理,下面那个条件展示的x不需要去关心。触发了change之后,会把相应的消息下发给search输入框的访问者们。

    当然,你用react你可能不会这样想了,你应该是在研究组件间怎么通信地好。

    上文提及使用传参避免全局耦合,然后在js里面通过控制class减少和css的耦合,和耦合相对的是内聚,出发点是重复代码,减少拷贝代码会有一个抽象和封装的过程:function -> 模块 -> 插件/框架,封装常用的还有封装成一个类,方便控制私有数据。这样可实现高内聚,除此方法,还有设计模式的思想,上面介绍了策略模式和访问者模式的原理和应用,以及在写代码的启示。

    转载于:https://www.cnblogs.com/ziyoublog/p/10314493.html

    展开全文
  • 获取数据和解析数据的方法尽量分开写,这样可以降低代码之间的耦合
  • 一、什么是耦合耦合度就是某模块(类)与其它模块(类)之间的关联、感知和依赖的程度,是衡量代码独立性的一个指标,也是软件工程设计及编码质量评价的一个标准。耦合的强度依赖于以下几个因素:(1)一个模块对另一个...
  •    <script src="jquery-1.2.js"></script>    $(document).ready (  function () { ... 使用attr访问自定义属性,减少javascript脚本中代码数据耦合    
  • 什么是代码耦合代码耦合的表现是改了一点毛发而牵动了全身,或者是想要...全局耦合就是几个类、模块共用了全局变量或者全局数据结构,特别是一个变量跨了几个文件。例如下面,在html里面定义了一个变量: 在...
  • 耦合

    2020-05-17 22:36:01
    尽量使用数据耦合,少用控制耦合,限制使用公共耦合,坚决不用内容耦合。 耦合的强弱 内容耦合 一个模块直接访问另一个模块的内部数据(汇编语言会有,比如都要用加法寄存器) 不是通过正常入口转到另一个模块的...
  • 为了减少冲突,我就把动态添加数据的那部分提取出去,并且想到使用属性和反射减少耦合。 结果就是:我在数据类里添加一个方法,就动态添加一个字典数据,不用去修改其他地方的代码。 属性类: [AttributeUsage...
  • 代码坏味道 - 耦合

    2016-03-05 00:12:00
    耦合 Feature Envy 症状: 方法访问其他类的对象的属性,而不是自己的。 成因: 最常见的问题就是由数据类引起的。 治疗: 多数时候,同时需要做出改变的code 应该在一起。 收益: 不合适的亲密 ...
  • 全局耦合就是几个类、模块共用了全局变量或者全局数据结构,特别是一个变量跨了几个文件 2、js/css/html的耦合 不推荐直接在js里面更改样式属性,而应该通过增删类来控制样式,这样子样式还是回归到css文件里面 ...
  • 背景:公司要写一个js方法,要求处理一写数据,然后显示在页面上。 这个是最开始写的,重要的是LCTShow(obj)方法,以后还要被其他人引用,所以要尽量做到低耦合。但是很明先,这段代码是一段失败的代码 document...
  • 目录一、效果演示二、分步理解(一)直接查询1. 查询一条结果2. 查询多条结果(二)局部查询 ...jq1.jsp全部代码: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%...
  • 耦合分类

    2019-10-17 14:58:45
    耦合分类内容耦合公共耦合外部耦合控制耦合标记耦合数据耦合非直接耦合 内容耦合 一个模块直接引用另一个模块的内部代码,典型例子就是:一个模块把所有功能做完,没有任何拆分; 公共耦合 一组模块共同引用一个全局...
  • 应用模板匹配程序以系统地检测地震记录中的地面耦合电波的代码。 此代码实现Scipy,Numpy,Obspy和Multiprocessing函数。 无线电波的检测是通过执行detect_peaks函数执行的,该函数由Duarte&Watanabe在2018年创建...
  • 1、反射使用的背景  最近在做一个功能,就是实现...,这样就相当于维护了两套代码,发送功能和发送格式配置代码耦合太严重,一直想着重构这个功能,后来有时间了,开始考虑重构了。  一直在想我的发送功能是固定...
  • 软件项目实训及课程设计指导——如何降低软件系统中程序类之间耦合关系(上篇)1、分离软件应用系统中各个模块类的接口定义和对应接口的具体功能实现面向对象程序类设计的五大原则中的"开放-封闭原则" ( OCP,Open-...
  • 关于耦合

    2020-04-03 13:35:51
    2.公共耦合:若一组模块都访问同一个公共数据环境,则他们之间的耦合就称谓公共耦合。公共的数据环境可以是全局数据结构,共享通信区,内存的公共覆盖区等。 3.外部耦合:一组模块都访问同一全局简单变量...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,738
精华内容 1,495
关键字:

数据耦合代码