敏捷软件开发_实例1<二>

这本书的实例非常好,给了我非常多的启发。主要讲了两个实例,咖啡机和薪水支付实例,咖啡机实例比较简单并没有用什么设计模式,薪水支付实例用了很多设计模式,包括后面的打包等。

咖啡机实例

做一个使用咖啡机的软件,驱动接口已经被写好。

咖啡机的硬件包括:

  • 加热器加热棒(开关)
  • 保温盘加热棒(开关)
  • 保温盘传感器(保温盘空,杯子空,杯子不空)
  • 加热器传感器(有水,没水)
  • 冲煮按钮(开关)
  • 指示灯(开关)
  • 减压阀门(开关)

咖啡机的冲煮流程:

  • 咖啡机一次煮12杯咖啡,
  • 咖啡加入过滤器,过滤器加入支架,支架放到咖啡机。
  • 倒入12杯水到滤水器,按下冲煮,水杯加热至沸腾,水蒸气碰洒到过滤器,形成水滴到咖啡壶,咖啡壶发现有水保温,
  • 拿走水杯,停止工作。

方案一

建立一个咖啡机超类,关联各个硬件类。这个方案是非常丑陋的,这不是根据行为划分的,有些类,比如light没有任何变量,仅仅调用了驱动接口,这种类叫水蒸气类。没有意义

方案二

按照行为来划分,要确定一些类有没有意义,只需要确定谁使用他们,而且要到业务底层去看。把问题的本质和细节分离,忘掉一切,最根本的问题是如何煮咖啡。如何煮咖啡,将热水倒到咖啡上,把冲泡好的咖啡收集起来。水从哪来?咖啡放到哪里?那么就有两个类:热水类和支架类,大大多数人会考虑热水流到支架中,这是比较错误的,软件的行为应该按照软件的行为给出而不是基于一些物理关系。还需要考虑用户界面,这样就有三个类。

  • 谁使用
  • 最根本的问题
  • 软件行为而不是物理行为

用例

  1. 按下冲煮,启动水流,支架做好准备,都准备好就开始煮咖啡
  2. 接受器具准备好没有
  3. 冲煮完成
  4. 咖啡喝完

a,b,c,d表示四种逻辑:

  • a 表示:用户按下冲煮,确保支架中有咖啡壶放在保温杯上,热水器中已经加满了水,然后才开始煮咖啡
  • b 表示:如果正在股咖啡的过程中咖啡壶被拿走,则必须中断咖啡流,停止送热水,再次放回咖啡壶继续煮咖啡
  • c 表示:热水器中传感器告诉我们水用完了就停止煮咖啡,同时告诉用户和支架(保温盘)已经停止煮咖啡
  • d 表示:冲煮结束时并且一个空的咖啡壶放在支架上(保温盘),热水器应该知道这个消息,同时用户也应该知道这个消息

这样整个咖啡机的抽象就完成了,按职责划分,各司其职。这三个抽象类不能知道任何关于咖啡机的任何信息。这就是依赖倒置原则。

系统的控制流如何检测传感器呢?是选择线程还是轮询。最好的总是假设消息都是可以异步发送的,就像存在有独立的线程一样,把使用轮询还是线程的决策推迟到最后一刻。

这样设置了一个接口,main()程序就待在一个循环中,不停地一遍遍调用这个方法实现轮询。

public static void Main(string[] args)
{
CoffeeMakerAPI api = new M4CoffeeMakerAPI();
M4UserInterface ui = new M4UserInterface(api);
M4HotWaterSOurce hws = new M4HotWaterSOurce(api);
M4ContainmentVessel cv = new M4ContainmentVessel(api);
ui.Init(hws,cv);
hws.Init(ui,cv);
cv.Init(hws,ui);
while(true)
{
ui.Poll();
hws.Poll();
cv.Poll();
}
}

依赖倒置,不允许高层的咖啡制作中依赖底层实现。

薪水支付实例

该案例主要有做一个薪水支付系统,主要有三类员工

  • 临时工:基本时薪,超过8小时加班时间1.5倍工资,每天有考勤卡,每周5结算。
  • 正式员工:月薪,每个月最后一天结算。
  • 经理:月薪,每月最后一天结算,有项目提成,每隔一周的周五结算,加入公会扣会费。
  • 公会会费分服务费和会费:会费每周都有从薪水中扣除,服务费从下个月薪水中扣除。
  • 薪水支付方式:可以选择支票邮寄到家,支票保存自取,直接存入银行账号。
  • 薪水支付每天运行一次,在当天为相应的雇员进行支付,上一次支付到本次支付应付的数额。

用例:

  • 新增雇员:雇员号,姓名,地址。(零时工,正式员工,经理)
  • 删除雇员:雇员号
  • 登记考勤卡:雇员号,日期,小时
  • 登记销售凭条:雇员号,日期,销售记录
  • 登记公会服务费:公会成员,服务费用
  • 更改雇员细则:更改姓名,更改地址,更改每小时报酬,更改薪水,更改提成,更改支付方式,加入公会,离开公会

设计类和结构:

通过迭代的方式进行实现。数据库是实现细节,应该尽推迟数据库的设计。通过用例来推导出应该有哪些类。

从用例的角度设计

  • 新增雇员

把每一项工作划分为自己的类中。这样有可能会创建三个雇员类,但是分析一下就会发现变化的东西太多了,正式由于雇员变化的东西引发雇员类型的改变,只需要将变化的东西抽象出来,在更改雇员细则时改变这些变化的东西就可以改变雇员类型。

  • 登记考勤卡

考勤卡和雇员应该是聚合的关系

  • 登记销售凭条

销售凭条和雇员也应该是聚合的关系

  • 登机工会服务费

工会服务费维护着工会会员的编号,因此系统必须要把工会成员和雇员标识联系起俩,推迟这一行为。公会成员和服务费也是聚合的关系

  • 更改雇员细则

这是由多个更改策略组合而成。

最后各个类之间的关系

从程序运行的角度补充细节

  • 运行薪水支付系统:找到所有进行支付的雇员,确定扣款额,根据他们的支付方式支付。
  • 抽象出变化的东西:雇员的支付类别抽象,支付时间抽象



  • 工会服务费抽象



实现

  • 事务

事务是使用命令模式。

  • 增加雇员事务,雇员有三个类型,所以使用模板模式来实现增加雇员,此处模板模式的唯一任务就是创建对象

public abstract class AddEmployeeTransaction : Transaction
{
private readonly int empid;
private readonly string name;
private readonly string address; public AddEmployeeTransaction(int empid,
string name, string address, PayrollDatabase database)
: base (database)
{
this.empid = empid;
this.name = name;
this.address = address;
} protected abstract
PaymentClassification MakeClassification();
protected abstract
PaymentSchedule MakeSchedule(); public override void Execute()
{
PaymentClassification pc = MakeClassification();
PaymentSchedule ps = MakeSchedule();
PaymentMethod pm = new HoldMethod(); Employee e = new Employee(empid, name, address);
e.Classification = pc;
e.Schedule = ps;
e.Method = pm;
database.AddEmployee(e);
} public override string ToString()
{
return String.Format("{0} id:{1} name:{2} address:{3}", GetType().Name, empid, name,address);
}
} public class AddSalariedEmployee : AddEmployeeTransaction
{
private readonly double salary; public AddSalariedEmployee(int id, string name, string address, double salary, PayrollDatabase database)
: base(id, name, address, database)
{
this.salary = salary;
} protected override
PaymentClassification MakeClassification()
{
return new SalariedClassification(salary);
} protected override PaymentSchedule MakeSchedule()
{
return new MonthlySchedule();
}
}
  • 删除雇员

提供雇员id,去数据库删除雇员,没啥好说的。

  • 考勤卡、销售凭条、服务费

考勤卡:需要参数,雇员id,日期,工作时间



public class TimeCard
{
private readonly DateTime date;
private readonly double hours; public TimeCard(DateTime date, double hours)
{
this.date = date;
this.hours = hours;
} public double Hours
{
get { return hours; }
} public DateTime Date
{
get { return date; }
}
}
public class TimeCardTransaction : Transaction
{
private readonly DateTime date;
private readonly double hours;
private readonly int empId; public TimeCardTransaction(DateTime date, double hours, int empId, PayrollDatabase database)
: base(database)
{
this.date = date;
this.hours = hours;
this.empId = empId;
} public override void Execute()
{
Employee e = database.GetEmployee(empId); if (e != null)
{
HourlyClassification hc =
e.Classification as HourlyClassification; if (hc != null)
hc.AddTimeCard(new TimeCard(date, hours));
else
throw new ApplicationException(
"Tried to add timecard to" +
"non-hourly employee");
}
else
throw new ApplicationException(
"No such employee.");
}
}

其他两种与这类似

  • 更改雇员属性

更改雇员属性由多个事务集合而成







改名字事务:

public abstract class ChangeEmployeeTransaction : Transaction
{
private readonly int empId; public ChangeEmployeeTransaction(int empId, PayrollDatabase database)
: base (database)
{
this.empId = empId;
} public override void Execute()
{
Employee e = database.GetEmployee(empId); if(e != null)
Change(e);
else
throw new ApplicationException(
"No such employee.");
} protected abstract void Change(Employee e);
}
public class ChangeNameTransaction
: ChangeEmployeeTransaction
{
private readonly string newName; public ChangeNameTransaction(int id, string newName, PayrollDatabase database)
: base(id, database)
{
this.newName = newName;
} protected override void Change(Employee e)
{
e.Name = newName;
}
}

更改雇员类别







public abstract class ChangeClassificationTransaction
: ChangeEmployeeTransaction
{
public ChangeClassificationTransaction(int id, PayrollDatabase database)
: base (id, database)
{} protected override void Change(Employee e)
{
e.Classification = Classification;
e.Schedule = Schedule;
} protected abstract
PaymentClassification Classification { get; }
protected abstract PaymentSchedule Schedule { get; }
}
public class ChangeHourlyTransaction
: ChangeClassificationTransaction
{
private readonly double hourlyRate; public ChangeHourlyTransaction(int id, double hourlyRate, PayrollDatabase database)
: base(id, database)
{
this.hourlyRate = hourlyRate;
} protected override PaymentClassification Classification
{
get { return new HourlyClassification(hourlyRate); }
} protected override PaymentSchedule Schedule
{
get { return new WeeklySchedule(); }
}
}
public class ChangeSalariedTransaction : ChangeClassificationTransaction
{
private readonly double salary; public ChangeSalariedTransaction(int id, double salary, PayrollDatabase database)
: base(id, database)
{
this.salary = salary;
} protected override PaymentClassification Classification
{
get { return new SalariedClassification(salary); }
} protected override PaymentSchedule Schedule
{
get { return new MonthlySchedule(); }
}
}
public class ChangeCommissionedTransaction
: ChangeClassificationTransaction
{
private readonly double baseSalary;
private readonly double commissionRate; public ChangeCommissionedTransaction(int id, double baseSalary, double commissionRate, PayrollDatabase database)
: base(id, database)
{
this.baseSalary = baseSalary;
this.commissionRate = commissionRate;
} protected override PaymentClassification Classification
{
get { return new CommissionClassification(baseSalary, commissionRate); }
} protected override PaymentSchedule Schedule
{
get { return new BiWeeklySchedule(); }
}
}

改变方法和改变工会实现方式基本与改变雇佣类别相似

改变支付方法:

public abstract class ChangeMethodTransaction : ChangeEmployeeTransaction
{
public ChangeMethodTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{} protected override void Change(Employee e)
{
PaymentMethod method = Method;
e.Method = method;
} protected abstract PaymentMethod Method { get; }
}
public class ChangeMailTransaction : ChangeMethodTransaction
{
public ChangeMailTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
} protected override PaymentMethod Method
{
get { return new MailMethod("3.14 Pi St"); }
} }
public class ChangeHoldTransaction : ChangeMethodTransaction
{
public ChangeHoldTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
} protected override PaymentMethod Method
{
get { return new HoldMethod(); }
} }
public class ChangeDirectTransaction : ChangeMethodTransaction
{
public ChangeDirectTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{
} protected override PaymentMethod Method
{
get { return new DirectDepositMethod("Bank -1", "123"); }
} }

改变工会:

public abstract class ChangeAffiliationTransaction : ChangeEmployeeTransaction
{
public ChangeAffiliationTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{} protected override void Change(Employee e)
{
RecordMembership(e);
Affiliation affiliation = Affiliation;
e.Affiliation = affiliation;
} protected abstract Affiliation Affiliation { get; }
protected abstract void RecordMembership(Employee e);
}
public class ChangeUnaffiliatedTransaction : ChangeAffiliationTransaction
{
public ChangeUnaffiliatedTransaction(int empId, PayrollDatabase database)
: base(empId, database)
{} protected override Affiliation Affiliation
{
get { return new NoAffiliation(); }
} protected override void RecordMembership(Employee e)
{
Affiliation affiliation = e.Affiliation;
if(affiliation is UnionAffiliation)
{
UnionAffiliation unionAffiliation =
affiliation as UnionAffiliation;
int memberId = unionAffiliation.MemberId;
database.RemoveUnionMember(memberId);
}
}
}
public class ChangeMemberTransaction : ChangeAffiliationTransaction
{
private readonly int memberId;
private readonly double dues; public ChangeMemberTransaction(int empId, int memberId, double dues, PayrollDatabase database)
: base(empId, database)
{
this.memberId = memberId;
this.dues = dues;
} protected override Affiliation Affiliation
{
get { return new UnionAffiliation(memberId, dues); }
} protected override void RecordMembership(Employee e)
{
database.AddUnionMember(memberId, e);
}
}
  • 支付薪水









支付月薪:

public class PaydayTransaction : Transaction
{
private readonly DateTime payDate;
private Hashtable paychecks = new Hashtable(); public PaydayTransaction(DateTime payDate, PayrollDatabase database)
: base (database)
{
this.payDate = payDate;
} public override void Execute()
{
ArrayList empIds = database.GetAllEmployeeIds(); foreach(int empId in empIds)
{
Employee employee = database.GetEmployee(empId);
if (employee.IsPayDate(payDate))
{
DateTime startDate =
employee.GetPayPeriodStartDate(payDate);
Paycheck pc = new Paycheck(startDate, payDate);
paychecks[empId] = pc;
employee.Payday(pc);
}
}
} public Paycheck GetPaycheck(int empId)
{
return paychecks[empId] as Paycheck;
}
}
public class MonthlySchedule : PaymentSchedule
{
private bool IsLastDayOfMonth(DateTime date)
{
int m1 = date.Month;
int m2 = date.AddDays(1).Month;
return (m1 != m2);
} public bool IsPayDate(DateTime payDate)
{
return IsLastDayOfMonth(payDate);
} public DateTime GetPayPeriodStartDate(DateTime date)
{
int days = 0;
while(date.AddDays(days - 1).Month == date.Month)
days--; return date.AddDays(days);
} public override string ToString()
{
return "monthly";
}
}

其中有paycheck类。该类有日期,总薪酬,服务扣费,实际薪酬

  • 临时工

      public class HourlyClassification : PaymentClassification
    {
    private double hourlyRate; private Hashtable timeCards = new Hashtable(); public HourlyClassification(double rate)
    {
    this.hourlyRate = rate;
    } public double HourlyRate
    {
    get { return hourlyRate; }
    } public TimeCard GetTimeCard(DateTime date)
    {
    return timeCards[date] as TimeCard;
    } public void AddTimeCard(TimeCard card)
    {
    timeCards[card.Date] = card;
    } public override double CalculatePay(Paycheck paycheck)
    {
    double totalPay = 0.0;
    foreach(TimeCard timeCard in timeCards.Values)
    {
    if(DateUtil.IsInPayPeriod(timeCard.Date,
    paycheck.PayPeriodStartDate,
    paycheck.PayPeriodEndDate))
    totalPay += CalculatePayForTimeCard(timeCard);
    }
    return totalPay;
    } private double CalculatePayForTimeCard(TimeCard card)
    {
    double overtimeHours = Math.Max(0.0, card.Hours - 8);
    double normalHours = card.Hours - overtimeHours;
    return hourlyRate * normalHours +
    hourlyRate * 1.5 * overtimeHours;
    } public override string ToString()
    {
    return String.Format("${0}/hr", hourlyRate);
    }
    }
    public class WeeklySchedule : PaymentSchedule
    {
    public bool IsPayDate(DateTime payDate)
    {
    return payDate.DayOfWeek == DayOfWeek.Friday;
    } public DateTime GetPayPeriodStartDate(DateTime date)
    {
    return date.AddDays(-6);
    } public override string ToString()
    {
    return "weekly";
    }
    } public class Employee
    {
    private readonly int empid;
    private string name;
    private readonly string address;
    private PaymentClassification classification;
    private PaymentSchedule schedule;
    private PaymentMethod method;
    private Affiliation affiliation = new NoAffiliation(); public Employee(int empid, string name, string address)
    {
    this.empid = empid;
    this.name = name;
    this.address = address;
    } public string Name
    {
    get { return name; }
    set { name = value; }
    } public string Address
    {
    get { return address; }
    } public PaymentClassification Classification
    {
    get { return classification; }
    set { classification = value; }
    } public PaymentSchedule Schedule
    {
    get { return schedule; }
    set { schedule = value; }
    } public PaymentMethod Method
    {
    get { return method; }
    set { method = value; }
    } public Affiliation Affiliation
    {
    get { return affiliation; }
    set { affiliation = value; }
    } public bool IsPayDate(DateTime date)
    {
    return schedule.IsPayDate(date);
    } public void Payday(Paycheck paycheck)
    {
    //计算总的薪资
    double grossPay = classification.CalculatePay(paycheck);
    //计算扣除薪资
    double deductions = affiliation.CalculateDeductions(paycheck);
    //计算实际薪资
    double netPay = grossPay - deductions;
    paycheck.GrossPay = grossPay;
    paycheck.Deductions = deductions;
    paycheck.NetPay = netPay;
    //通过支付方式支付
    method.Pay(paycheck);
    } public DateTime GetPayPeriodStartDate(DateTime date)
    {
    return schedule.GetPayPeriodStartDate(date);
    } public int EmpId
    {
    get { return empid; }
    } public override string ToString()
    {
    StringBuilder builder = new StringBuilder();
    builder.Append("Emp#: ").Append(empid).Append(" ");
    builder.Append(name).Append(" ");
    builder.Append(address).Append(" ");
    builder.Append("Paid ").Append(classification).Append(" ");
    builder.Append(schedule);
    builder.Append(" by ").Append(method);
    return builder.ToString();
    }
    } public class UnionAffiliation : Affiliation
    {
    private Hashtable charges = new Hashtable();
    private int memberId;
    private readonly double dues; public UnionAffiliation(int memberId, double dues)
    {
    this.memberId = memberId;
    this.dues = dues;
    } public UnionAffiliation()
    : this(-1, 0.0)
    {} public ServiceCharge GetServiceCharge(DateTime time)
    {
    return charges[time] as ServiceCharge;
    } public void AddServiceCharge(ServiceCharge sc)
    {
    charges[sc.Time] = sc;
    } public double Dues
    {
    get { return dues; }
    } public int MemberId
    {
    get { return memberId; }
    } public double CalculateDeductions(Paycheck paycheck)
    {
    double totalDues = 0; int fridays = NumberOfFridaysInPayPeriod(
    paycheck.PayPeriodStartDate, paycheck.PayPeriodEndDate);
    totalDues = dues * fridays; foreach(ServiceCharge charge in charges.Values)
    {
    if(DateUtil.IsInPayPeriod(charge.Time,
    paycheck.PayPeriodStartDate,
    paycheck.PayPeriodEndDate))
    totalDues += charge.Amount;
    } return totalDues;
    } private int NumberOfFridaysInPayPeriod(
    DateTime payPeriodStart, DateTime payPeriodEnd)
    {
    int fridays = 0;
    for (DateTime day = payPeriodStart;
    day <= payPeriodEnd; day = day.AddDays(1))
    {
    if (day.DayOfWeek == DayOfWeek.Friday)
    fridays++;
    }
    return fridays;
    }
    }
  • 主程序



总结

到目前为止基本功能已经实现,仅仅只是用了模板,空值,命令等设计模式,下一篇将会进一步使用更多的设计模式进行打包处理。

敏捷软件开发_实例1<二>的更多相关文章

  1. 敏捷软件开发_实例2<四>

    敏捷软件开发_实例2 上一章中对薪水支付案例的用例和类做了详细的阐述,在本篇会介绍薪水支付案例包的划分和数据库,UI的设计. 包的划分 一个错误包的划分 为什么这个包是错误的: 如果对classifi ...

  2. 敏捷软件开发_设计原则<三>

    敏捷软件开发_设计原则 单一职责原则(single responsibilities principle,SRP) 原理:一个类应该只有一个变化 分离职责:如果不耦合的职责那么很简单,如果两个职责耦合 ...

  3. 敏捷软件开发_UML<一>

    敏捷软件开发_UML  所看书籍是:敏捷软件开发_原则.模式与实践_C#版(美)马丁著,这本书写的非常棒,感谢作者.该归纳总结的过程按照我读的顺序写. UML  在建造桥梁,零件,自动化设备之前需要建 ...

  4. 敏捷软件开发VS传统软件工程

    敏捷软件开发:又称敏捷开发,是一种从1990年代开始逐渐引起广泛关注的一些新兴软件开发方法,是一种应对快速变化的需求的一种软件开发能力. 与传统软件工程相比,它们的具体名称.理念.过程.术语都不尽相同 ...

  5. 敏捷软件开发 VS. 传统软件工程

    敏捷软件开发 VS. 传统软件工程 软件工程这一术语1968年被提出,之后美国软件工程专家巴利·玻姆对十多年间研究软件工程的专家学者们提出的一些准则与信条,于1983年对提出软件工程的七条基本定理,将 ...

  6. 敏捷软件开发vs传统软件开发

    摘要 本文介绍了传统软件开发(着重介绍了传统软件开发中常用的瀑布模型)和敏捷软件开发,以及敏捷开发和传统开发的对比. 一.传统软件开发 比较常用的几种传统软件开发方法:瀑布式开发.迭代式开发.螺旋开发 ...

  7. 敏捷软件开发:原则、模式与实践——第12章 ISP:接口隔离原则

    第12章 ISP:接口隔离原则 不应该强迫客户程序依赖并未使用的方法. 这个原则用来处理“胖”接口所存在的缺点.如果类的接口不是内敛的,就表示该类具有“胖”接口.换句话说,类的“胖”接口可以分解成多组 ...

  8. 敏捷软件开发:原则、模式与实践——第10章 LSP:Liskov替换原则

    第10章 LSP:Liskov替换原则    Liskov替换原则:子类型(subtype)必须能够替换掉它们的基类型(base type). 10.1 违反LSP的情形 10.1.1 简单例子 对L ...

  9. AgileEAS.NET SOA中间件平台/敏捷软件开发平台

    AgileEAS.NET SOA中间件平台/敏捷软件开发平台 最新下载 一.前言 AgileEAS.NET SOA中间件平台,简称EAS.NET,是基于敏捷并行开发思想和Microsoft .Net构 ...

随机推荐

  1. 多线程六 同步容器&并发容器

    同步容器(使用的是synchronized,并且不一定是百分百安全) 本篇续 -- 线程之间的通信 ,介绍java提供的并发集合,既然正确的使用wait和notify比较困难,java平台为我们提供了 ...

  2. ASP.NET Core 2.2 WebApi 系列【二】使用EF CodeFirst创建数据库

    Code First模式 Code First是指"代码优先"或"代码先行". Code First模式将会基于编写的类和配置,自动创建模型和数据库. 一.准备 ...

  3. arcgis api 4.x for js 图层拓展篇之mapvLayer(附源码下载)

    因为在项目开发过程中,使用的arcgis js api版本是4.7,并不能支持客户端渲染热力图,想到arcgis js api 4.x的渲染是基于canvas,故琢磨着是否能借助类似于mapV.ech ...

  4. Android Service 启动流程

    执行顺序 : startService -> bindService -> unbindService -> stopService 回调的结果为: 执行顺序 : startServ ...

  5. LeetCode刷题191118

    博主渣渣一枚,刷刷leetcode给自己瞅瞅,大神们由更好方法还望不吝赐教.题目及解法来自于力扣(LeetCode),传送门. 算法: 给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按 ...

  6. 41-data-packed volume container

    在上一节的例子中 volume container 的数据归根到底还是在 host 里,有没有办法将数据完全放到 volume container 中,同时又能与其他容器共享呢? 当然可以,通常我们称 ...

  7. 苏州市java岗位的薪资状况(2)

    上一篇已经统计出了起薪最高的top 10: 接着玩,把top 10 中所有职位的详细信息爬取下来.某一职位的详情是这样: 我们需要把工作经验.学历.职能.关键字爬取下来. from urllib.re ...

  8. jstree级联禁用后代节点的选择框

    用jstree+jquery,做的树形展示. 这个话题,在Stack Overflow上有问答,要获取要禁用的节点,然后用获取子节点方法遍历后代节点,设置禁用选择框. 之后发现,jstree的获取子节 ...

  9. JVM-10-JAVA 四种引用类型

    JAVA  四中引用类型   1.  强引用 在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用. 当一个对象被强引用变量引用时,它处于可达状态,不可能被垃圾回 ...

  10. React 中this.setStat是批量执行的, 它发现做三次是多余的,所以只执行一次

    16==> this.setStat是批量执行的 它发现做三次是多余的,所以只执行一次 import React, { Component } from "react"; e ...