云计算百科
云计算领域专业知识百科平台

LeetCode100+java面经+DDD领域驱动设计理解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

思考:学习测开知识并搭一个项目,后续全栈,agent,测开都能投

盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

思路:双指针往中间走,每次计算当前容积,要求最大水容量,左右哪边小移动哪边
class Solution {

public int maxArea(int[] height) {

int left=0;

int right=height.length-1;

int max=0;

while(left<right){

int cur=-1;

if(height[left]<height[right]){

cur=height[left]*(right-left);

left++;

}

else{

cur=height[right]*(right-left);

right–;

}

if(cur>max)max=cur;

}

return max;

}

}

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

思路:Hashmap
易错:不能一开始就建立数字->索引,当target=2*nums[i]会出重复
class Solution {

public int[] twoSum(int[] nums, int target) {

Map<Integer,Integer> map=new HashMap<>();

for(int i=0;i<nums.length;i++){

if(map.containsKey(target-nums[i])){

return new int[]{i,map.get(target-nums[i])};

}

map.put(nums[i],i);

}

return null;

}

}

5.Java设计模式:讲一下策略模式。
是一种代码设计模式,主要目的就是为了消除大量的ifelse代码,比如一个支付功能,支持微信支付宝等,最简单写法是利用ifelse根据支付类型走对应支付逻辑,但利用策略模式,可以先定义一个支付接口,对于不同的支付行为实现不同的支付类,然后搭配工厂模式写一个map进行动态的支付策略的路由,在调用的时候业务代码就变得很干净
好处:

  • 消灭了 if-else:你的主流程代码变得非常干净、清晰。

  • 符合“开闭原则”:下次加个“银联支付”。你不需要改动任何现有的代码。你只需要新建一个 UnionPayStrategy implements PayStrategy 类,打上 @Service("UNION") 注解,Spring 自动就把它注册进去了。业务立马就能用!

  • 职责单一:微信支付出 Bug 了,你只需要去 WechatPayStrategy 里找,绝对不会碰到支付宝的代码

  • 6 .架构设计:了解 DDD(Domain-Driven Design 领域驱动设计)吗?

    “DDD 并不是一套技术框架,而是一种应对复杂业务架构的方法论。传统的 MVC 模式把所有的逻辑堆在 Service 里,容易产生‘上帝类’。

    引入 DDD 后,我们会根据业务边界划分出不同的**‘限界上下文’(微服务)。在代码实现上,我们会采用四层架构,把核心业务逻辑沉淀到‘领域层’,通过‘聚合根’保证数据一致性,通过‘领域事件’**实现服务间的解耦。

    我曾经在一个电商项目中应用过 DDD,把原本揉在一起的订单、库存、营销系统拆分,用事件驱动串联,大大降低了系统的耦合度,提升了代码的可维护性。”

    MVC和DDD区别

    在 MVC 中,所有的业务逻辑(计算价格、判断库存、校验状态)都被塞进了 Service 层,而 Entity(数据库实体)里只有 getter 和 setter。

    假设业务规则是:下单时,必须校验库存是否充足,如果充足则扣减;如果订单总金额超过 500 元,还需要打 9 折。

    一、 传统 MVC 写法(贫血模型)

    在 MVC 架构下,我们的实体类(Entity)通常只是个“数据袋”,所有的逻辑全在 Service 里。

    1. Entity(贫血模型)

    java

    public class Order {
    private Long id;
    private Double totalAmount;
    private Integer status;
    // 只有 getter 和 setter
    public Double getTotalAmount() { return totalAmount; }
    public void setTotalAmount(Double totalAmount) { this.totalAmount = totalAmount; }
    // … 其他 getter/setter
    }
    public class Product {
    private Long id;
    private Integer stock;
    // 只有 getter 和 setter
    public Integer getStock() { return stock; }
    public void setStock(Integer stock) { this.stock = stock; }
    }

    2. Service(上帝类)

    所有的业务逻辑、判断、算钱都在这里,随着需求增加,这个类会变得无比臃肿。

    java

    @Service
    public class OrderService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private OrderMapper orderMapper;
    public void createOrder(Long productId, Integer quantity) {
    // 1. 查数据库
    Product product = productMapper.selectById(productId);

    // 2. 业务判断:校验库存
    if (product.getStock() < quantity) {
    throw new RuntimeException("库存不足");
    }

    // 3. 业务逻辑:扣减库存 (直接修改属性)
    product.setStock(product.getStock() – quantity);
    productMapper.updateById(product);

    // 4. 业务逻辑:计算金额
    Double amount = 100.0 * quantity;
    if (amount > 500) {
    amount = amount * 0.9; // 满500打9折
    }

    // 5. 创建订单
    Order order = new Order();
    order.setTotalAmount(amount);
    order.setStatus(1);
    orderMapper.insert(order);

    System.out.println("下单成功,金额:" + amount);
    }

    }

    痛点:未来如果产品经理说“VIP用户积分超过1000也可以打折”,你得继续在这个 createOrder 里面写 if-else。数据(Entity)和操作数据的行为(Service)分家了,非常容易改出 Bug。


    二、 DDD 写法(充血模型)

    在 DDD 中,我们强调**“谁的数据,谁自己管”**。我们把计算金额、扣减库存的行为,直接搬到实体内部去。

    1. Domain 层(充血模型)

    实体不再只是数据袋,它有了自己的“灵魂”(行为)。

    java

    // 商品实体:自己负责扣库存
    public class Product {
    private Long id;
    private Integer stock;
    public Integer getStock() { return stock; }
    // 核心业务逻辑:扣减库存由商品自己负责
    public void deductStock(int quantity) {
    if (this.stock < quantity) {
    throw new BusinessException(“商品库存不足”);
    }
    this.stock -= quantity;
    }
    }
    // 订单实体:自己负责算钱
    public class Order {
    private Long id;
    private Double totalAmount;
    public Double getTotalAmount() { return totalAmount; }
    // 核心业务逻辑:计算总价由订单自己负责
    public void calculateTotalAmount(Double unitPrice, int quantity) {
    double amount = unitPrice * quantity;
    if (amount > 500) {
    amount = amount * 0.9;
    }
    this.totalAmount = amount;
    }
    }

    2. Application 层(应用服务)

    应用服务变成了“光杆司令”,它只负责调度:拿数据 -> 调领域模型 -> 存数据。没有具体的业务判断。

    java

    @Service
    public class OrderApplicationService {
    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private OrderRepository orderRepository;
    @Transactional
    public void createOrder(Long productId, Integer quantity) {
    // 1. 获取聚合根(通过 Repository 查出来)
    Product product = productRepository.findById(productId);

    // 2. 委托给领域模型自己去扣库存(它内部有校验逻辑)
    product.deductStock(quantity);

    // 3. 创建订单并委托领域模型自己算钱
    Order order = new Order();
    order.calculateTotalAmount(100.0, quantity);

    // 4. 持久化
    productRepository.save(product);
    orderRepository.save(order);
    }

    }


    三、 用代码看本质区别

    对比一下上面两段代码:

  • 在 MVC 中:OrderService 像个操碎了心的老母亲,不但要查数据库,还要管算账、管扣库存。如果算账逻辑出错了,你需要在一个几千行的 Service 里找半天。

  • 在 DDD 中:OrderApplicationService 就像个项目经理,它只说一句:order.calculateTotalAmount(…) 和 product.deductStock(…)。具体怎么算、怎么扣,让 Order 和 Product 这两个“专家(实体)”自己去搞定。

  • “谈谈你对 DDD、MVC 和微服务的理解?”

    “我认为这三者是不同维度的概念。

    MVC 是一种技术实现架构,它通过 Controller、Service、DAO 实现代码分层,但在复杂业务中,容易导致 Service 层变成‘上帝类’(贫血模型)。

    DDD 是一种应对复杂业务的方法论。它的核心是把业务逻辑收拢到领域模型中(充血模型),并通过划分‘限界上下文’和‘聚合根’来保证业务的高内聚。

    微服务 是一种物理部署架构。,如果没有 DDD 的指导,微服务很容易拆成‘分布式单体’。

    所以,实际工程中,我们通常用 DDD 的思想来划分微服务的边界,把每个限界上下文落地为一个微服务,然后用 MVC 或六边形架构在微服务内部实现代码分层。这样既有宏观的业务解耦,又有微观的技术实现。”

    7. Redis:Redis 为什么那么快?

  • 纯内存操作:基于内存,读写速度极快,避免了磁盘 I/O。

  • 单线程模型(针对命令执行):避免了多线程的上下文切换和锁竞争,保证了原子性(6.0 之后网络 I/O 是多线程,但核心命令执行仍是单线程)。

  • I/O 多路复用(epoll):一个线程可以同时处理大量网络连接,让 CPU 始终处于高利用率。

  • 高效的数据结构:如 SDS、跳表(ZSet)、哈希表(渐进式 rehash)等,都经过极致优化。

  • “比如在高并发秒杀场景下,每秒几万次的库存读取和扣减,传统数据库会因为行锁和磁盘 IO 直接崩溃。利用 Redis 的原子命令(如 DECR)在内存中瞬间完成,效率极高。”

    8. Spring:Spring 事务失效的场景有哪些?

    “Spring 事务失效的本质是因为 AOP 动态代理没有生效。常见场景有 5 种:

  • 方法非 public:Spring 事务默认只能拦截 public 方法。

  • 自调用:同一个类中,A 方法(无事务)调用 B 方法(有事务),因为走的是 this 引用,不经过代理类。

  • 异常被 catch 吞掉:或者抛出的是受检异常(Checked Exception),默认不回滚。

  • 类未被 Spring 管理:没有加 @Service 等注解。

  • 数据库引擎不支持:如 MySQL 的 MyISAM。”

  • 9. Java并发:CAS 的原理是什么?

    CAS(Compare and Swap)是一种无锁并发机制,包含三个操作数:内存位置 V、预期原值 A、新值 B。当且仅当 V 的值等于 A 时,才会将 V 更新为 B,否则不做任何操作,通常配合自旋循环重试。
    但在高并发下,CAS 有三大问题:ABA 问题(加版本号解决)、自旋开销大、只能保证一个变量原子性。

    10. Java并发:synchronized 的锁升级过程是怎样的?

    “为了减少锁带来的性能开销,JDK 1.6 引入了锁升级机制。锁的状态保存在对象头的 Mark Word 中,过程为:

  • 无锁:对象刚创建。

  • 偏向锁:只有一个线程访问时,直接在对象头记录线程 ID,下次进来直接放行,无锁化开销。

  • 轻量级锁:当有多个线程交替访问时,偏向锁升级为轻量级锁。线程通过 CAS 自旋获取锁,不会阻塞。

  • 重量级锁:如果自旋失败(并发冲突严重),升级为重量级锁,没抢到锁的线程进入 Monitor 的 EntryList 阻塞等待,由操作系统唤醒。”

  • 11. 场景题:如果接手了一堆 if-else 嵌套,里面还有一堆 try-catch 的代码,你会怎么优化重构?

    使用设计模式:如果 if-else 是业务类型判断,引入策略模式 + 工厂模式;如果是状态流转,使用状态模式;如果是参数校验,使用责任链模式。

    模式解决什么问题核心特征适用场景
    策略 + 工厂 消除平级的业务类型判断 选出唯一的算法去执行 支付方式、登录方式、打折计算
    状态模式 消除随状态改变的大量分支 内部状态改变引发行为改变 订单流转、电梯运行、红绿灯
    责任链模式 消除串行的前置校验或审批 像链条一样依次传递处理 参数校验、审批流、拦截器

    12. 场景题:如果按照产品经理的方案做出了技术方案并上线,结果发现系统出现了很严重的问题,这个时候你该怎么解决?

    “遇到线上事故,第一原则是**『先恢复,后排查,再复盘』**:

  • 快速止损:立即回滚最近的上线版本(前提是回滚安全),或者通过配置中心降级该功能开关,优先保证核心业务可用。

  • 定位问题:查看 APM 监控(如 SkyWalking)、日志错误率、数据库慢查询,排查是否因为流量突增、资源死锁或新代码的 Bug。

  • 高效修复:定位到 Bug 后,紧急拉分支修复,走绿色通道快速测试上线。

  • 事后复盘:写事故报告(RCA),分析根因,并补充监控告警、完善测试用例(特别是漏测的边界场景),优化发布流程(引入灰度发布)。”

  • 14. 缓存设计:怎么保证 Redis 和数据库的数据一致性?

    写操作:先更新数据库,再删除缓存
    读操作:先读缓存,未命中读 DB,然后回写缓存。
    兜底方案:针对高一致性业务,可以通过监听 MySQL 的 Binlog(如 Canal),将变更异步同步到 Redis

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » LeetCode100+java面经+DDD领域驱动设计理解
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!